@blimu/nestjs 1.2.1 → 1.2.2
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/dist/index.cjs +27 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +27 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -200,7 +200,8 @@ var JWKService = class {
|
|
|
200
200
|
logger = new import_common3.Logger(JWKService.name);
|
|
201
201
|
tokenVerifier;
|
|
202
202
|
/**
|
|
203
|
-
* Verify JWT token
|
|
203
|
+
* Verify JWT token issued by Blimu's main auth (environment/session tokens).
|
|
204
|
+
* Uses the configured API key and environment JWKS.
|
|
204
205
|
*/
|
|
205
206
|
async verifyToken(token) {
|
|
206
207
|
try {
|
|
@@ -223,6 +224,31 @@ var JWKService = class {
|
|
|
223
224
|
throw error;
|
|
224
225
|
}
|
|
225
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Verify JWT access token issued by an OAuth2 app (e.g. device code or authorization code flow).
|
|
229
|
+
* Uses the public OAuth JWKS endpoint; no API key required. Pass the OAuth app's client_id.
|
|
230
|
+
*/
|
|
231
|
+
async verifyOAuthToken(token, clientId) {
|
|
232
|
+
try {
|
|
233
|
+
this.logger.debug(
|
|
234
|
+
`\u{1F50D} Verifying OAuth token. Runtime API URL: ${this.config.baseURL}, clientId: ${clientId}`
|
|
235
|
+
);
|
|
236
|
+
const result = await this.tokenVerifier.verifyToken({
|
|
237
|
+
clientId,
|
|
238
|
+
token,
|
|
239
|
+
runtimeApiUrl: this.config.baseURL
|
|
240
|
+
});
|
|
241
|
+
this.logger.debug(`\u2705 OAuth token verified successfully`);
|
|
242
|
+
return result;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
245
|
+
this.logger.error(`\u274C OAuth token verification failed: ${errorMessage}`);
|
|
246
|
+
if (error instanceof Error && error.stack) {
|
|
247
|
+
this.logger.error(`Stack trace: ${error.stack}`);
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
226
252
|
/**
|
|
227
253
|
* Clear cache (useful for testing or key rotation)
|
|
228
254
|
*/
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/modules/blimu.module.ts","../src/config/blimu.config.ts","../src/guards/entitlement.guard.ts","../src/exceptions/blimu-forbidden.exception.ts","../src/services/jwk.service.ts","../src/decorators/entitlement.decorator.ts"],"sourcesContent":["// Main module\nexport * from './modules/blimu.module';\n\n// Configuration\nexport * from './config/blimu.config';\n\n// Guards\nexport * from './guards/entitlement.guard';\n\n// Decorators\nexport * from './decorators/entitlement.decorator';\n\n// Exceptions\nexport * from './exceptions/blimu-forbidden.exception';\n\n// Services\nexport * from './services';\n","import {\n Module,\n type DynamicModule,\n type Type,\n type ForwardReference,\n type InjectionToken,\n type OptionalFactoryDependency,\n} from '@nestjs/common';\n\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\nimport { EntitlementGuard } from '../guards/entitlement.guard';\nimport { JWKService } from '../services/jwk.service';\nimport { Blimu } from '@blimu/backend';\n\nconst DEFAULT_BASE_URL = 'https://api.blimu.dev';\n\n/**\n * Blimu NestJS Module\n *\n * This module provides entitlement checking capabilities and Blimu Runtime SDK integration\n * for NestJS applications. It can be configured synchronously or asynchronously.\n */\n@Module({})\nexport class BlimuModule {\n /**\n * Configure the Blimu module with static configuration\n *\n * @param config - The Blimu configuration object\n * @returns A configured dynamic module\n *\n * @example\n * Basic usage with default Request type:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRoot({\n * apiKey: 'your-api-secret-key',\n * baseURL: 'https://api.blimu.dev', // optional\n * environmentId: 'your-environment-id', // optional\n * timeoutMs: 30000, // optional\n * getUserId: (req) => req.user?.id, // required\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }), // optional\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Usage with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRoot<AuthenticatedRequest>({\n * apiKey: 'your-api-secret-key',\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRoot<TRequest = unknown>(config: BlimuConfig<TRequest>): DynamicModule {\n return {\n ...(config.global ? { global: true } : {}),\n module: BlimuModule,\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useValue: {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n },\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n }\n\n /**\n * Configure the Blimu module with async configuration\n *\n * This is useful when you need to load configuration from environment variables,\n * configuration services, or other async sources.\n *\n * @param options - Async configuration options\n * @returns A configured dynamic module\n *\n * @example\n * Using with ConfigService:\n * ```typescript\n * @Module({\n * imports: [\n * ConfigModule.forRoot(),\n * BlimuModule.forRootAsync({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * baseURL: configService.get('BLIMU_BASE_URL'),\n * environmentId: configService.get('BLIMU_ENVIRONMENT_ID'),\n * timeoutMs: configService.get('BLIMU_TIMEOUT_MS'),\n * getUserId: (req) => req.user?.id,\n * defaultEntitlementCtxResolver: (entitlementKey, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync<AuthenticatedRequest>({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom provider:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync({\n * imports: [MyConfigModule],\n * useFactory: async (myConfigService: MyConfigService) => {\n * const config = await myConfigService.getBlimuConfig();\n * return {\n * apiKey: config.apiKey,\n * baseURL: config.baseUrl,\n * environmentId: config.environmentId,\n * getUserId: (req) => req.user?.id,\n * };\n * },\n * inject: [MyConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync<TRequest = unknown>(options: {\n global?: boolean | undefined;\n useFactory: (...args: unknown[]) => Promise<BlimuConfig<TRequest>> | BlimuConfig<TRequest>;\n inject?: (InjectionToken | OptionalFactoryDependency)[];\n imports?: (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference<() => Type<unknown>>\n )[];\n }): DynamicModule {\n const additionalImports = options.imports ?? [];\n\n const module = {\n ...(options.global ? { global: true } : {}),\n module: BlimuModule,\n imports: [...additionalImports] as (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference\n )[],\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useFactory: async (...args: unknown[]) => {\n const configResult = options.useFactory(...args);\n const config = configResult instanceof Promise ? await configResult : configResult;\n return {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n };\n },\n ...(options.inject ? { inject: options.inject } : {}),\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n return module;\n }\n}\n","import type { EntitlementType, ResourceType } from '@blimu/types';\n/**\n * Configuration interface for Blimu NestJS integration\n */\nexport interface BlimuConfig<TRequest = unknown> {\n global?: boolean | undefined;\n /**\n * The API secret key for authenticating with Blimu Runtime API\n */\n apiKey: string;\n\n /**\n * The base URL for the Blimu Runtime API\n * @default 'https://api.blimu.dev'\n */\n baseURL?: string | undefined;\n\n /**\n * Environment ID for the Blimu environment\n * This will be used in future versions for environment-specific configurations\n */\n environmentId?: string | undefined;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeoutMs?: number | undefined;\n\n /**\n * Function to extract user ID from the request\n *\n * This function is called by the EntitlementGuard to determine which user\n * to check entitlements for. It should return the user ID as a string.\n *\n * @param request - The incoming HTTP request\n * @returns The user ID as a string, or a Promise that resolves to the user ID\n *\n * @example\n * ```typescript\n * // Extract from JWT token in Authorization header\n * getUserId: (req) => {\n * const token = req.headers.authorization?.replace('Bearer ', '');\n * const decoded = jwt.verify(token, secret);\n * return decoded.sub;\n * }\n *\n * // Extract from request.user (common with Passport.js)\n * getUserId: (req) => req.user?.id\n *\n * // Extract from custom header\n * getUserId: (req) => req.headers['x-user-id']\n * ```\n */\n getUserId: (request: TRequest) => string | Promise<string>;\n\n /**\n * Optional default entitlement context resolver for entitlement checks\n *\n * This function is used as a fallback when a decorator-specific resolver is not provided.\n * It receives an object with the entitlement key and parsed resource type, along with the request,\n * allowing for context-aware resolution.\n *\n * @param context - Object containing the entitlement and resource type (parsed from entitlement by splitting on ':')\n * @param request - The incoming HTTP request\n * @returns Entitlement context with resourceId and optionally amount, or a Promise that resolves to it\n *\n * @example\n * ```typescript\n * // Extract resourceId from path parameter\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * })\n *\n * // Context-aware resolution based on resource type\n * defaultEntitlementCtxResolver: ({ resourceType }, req) => {\n * if (resourceType === 'organization') {\n * return { resourceId: req.params.orgId };\n * }\n * return { resourceId: req.params.resourceId };\n * }\n *\n * // Extract from request body or query with amount for consumption\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.body?.resourceId || req.query?.resourceId,\n * amount: req.body?.amount, // Amount to consume for usage-based entitlements\n * })\n * ```\n */\n defaultEntitlementCtxResolver?: (\n context: { entitlement: EntitlementType; resourceType: ResourceType },\n request: TRequest,\n ) =>\n | { resourceId: string; amount?: number }\n | Promise<{ resourceId: string; amount?: number }>\n | undefined;\n}\n\n/**\n * Injection token for Blimu configuration\n */\nexport const BLIMU_CONFIG = Symbol('BLIMU_CONFIG');\n","import {\n type CanActivate,\n type ExecutionContext,\n ForbiddenException,\n Injectable,\n SetMetadata,\n Inject,\n} from '@nestjs/common';\nimport 'reflect-metadata';\n\nimport type { EntitlementType } from '@blimu/types';\nimport { BlimuForbiddenException } from '../exceptions/blimu-forbidden.exception';\nimport { Blimu } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\nexport const ENTITLEMENT_KEY = 'entitlement';\nexport const ENTITLEMENT_METADATA_KEY = Symbol('entitlement');\n\n/**\n * Entitlement context returned by the entitlementCtxResolver callback\n */\nexport interface EntitlementCtx {\n resourceId: string;\n amount?: number; // Amount to check against usage limit (for consumption)\n}\n\n/**\n * Metadata interface for entitlement checks\n */\nexport interface EntitlementMetadata<TRequest = unknown> {\n entitlementKey: EntitlementType;\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>;\n}\n\n/**\n * Sets entitlement metadata for a route handler\n * @internal This is used internally by the @Entitlement decorator\n */\nexport const SetEntitlementMetadata = <TRequest = unknown>(\n entitlementKey: string,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator =>\n SetMetadata(ENTITLEMENT_METADATA_KEY, {\n entitlementKey,\n entitlementCtxResolver,\n } as EntitlementMetadata<TRequest>);\n\n/**\n * Guard that checks if the authenticated user has the required entitlement on a resource\n *\n * This guard automatically:\n * 1. Extracts the user from the request\n * 2. Extracts the resource ID using the provided extractor function\n * 3. Calls the Blimu Runtime API to check entitlements\n * 4. Allows or denies access based on the result\n */\n@Injectable()\nexport class EntitlementGuard<TRequest = unknown> implements CanActivate {\n constructor(\n @Inject(BLIMU_CONFIG)\n private readonly config: BlimuConfig<TRequest>,\n @Inject(Blimu)\n private readonly runtime: Blimu,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<TRequest>();\n const handler = context.getHandler();\n const metadata = Reflect.getMetadata(ENTITLEMENT_METADATA_KEY, handler) as\n | EntitlementMetadata<TRequest>\n | undefined;\n\n if (!metadata) {\n // No entitlement check required\n return true;\n }\n\n // Extract user ID using the configured getUserId function\n let userId: string;\n try {\n userId = await this.config.getUserId(request);\n } catch {\n throw new ForbiddenException('Failed to extract user ID from request');\n }\n\n if (!userId) {\n throw new ForbiddenException('User ID is required for entitlement check');\n }\n\n // Resolve entitlement context from request\n // Priority: decorator resolver > default resolver > error\n let entitlementCtx: EntitlementCtx | undefined;\n if (metadata.entitlementCtxResolver) {\n entitlementCtx = await metadata.entitlementCtxResolver(request);\n } else if (this.config.defaultEntitlementCtxResolver) {\n // Parse resourceType from entitlementKey (format: \"resourceType:action\")\n const resourceType = metadata.entitlementKey.split(':')[0] || '';\n entitlementCtx = await this.config.defaultEntitlementCtxResolver(\n {\n entitlement: metadata.entitlementKey,\n resourceType,\n },\n request,\n );\n } else {\n throw new ForbiddenException('No entitlement context resolver available');\n }\n\n if (!entitlementCtx?.resourceId) {\n throw new ForbiddenException('Resource ID is required for entitlement check');\n }\n\n try {\n // Check entitlement\n const result = await this.runtime.entitlements.checkEntitlement({\n userId,\n entitlement: metadata.entitlementKey,\n resourceId: entitlementCtx.resourceId,\n ...(entitlementCtx.amount !== undefined ? { amount: entitlementCtx.amount } : {}),\n });\n\n if (!result.allowed) {\n throw new BlimuForbiddenException(\n result,\n metadata.entitlementKey,\n entitlementCtx.resourceId,\n userId,\n );\n }\n\n return true;\n } catch (error) {\n if (error instanceof BlimuForbiddenException || error instanceof ForbiddenException) {\n throw error;\n }\n\n // Log the error for debugging but don't expose internal details\n console.error('Entitlement check failed:', error);\n throw new ForbiddenException('Failed to verify entitlements');\n }\n }\n}\n","import { ForbiddenException } from '@nestjs/common';\nimport type { Schema } from '@blimu/backend';\nimport type { EntitlementType } from '@blimu/types';\n/**\n * Custom exception for Blimu entitlement check failures\n *\n * This exception extends NestJS's ForbiddenException and includes\n * the typed EntitlementCheckResult, providing detailed information\n * about why the entitlement check failed (roles, plans, limits, etc.)\n */\nexport class BlimuForbiddenException extends ForbiddenException {\n /**\n * The entitlement check result containing detailed failure information\n */\n public readonly entitlementResult: Schema.EntitlementCheckResult;\n\n /**\n * The entitlement key that was checked\n */\n public readonly entitlementKey: EntitlementType;\n\n /**\n * The resource ID that was checked\n */\n public readonly resourceId: string;\n\n /**\n * The user ID that was checked\n */\n public readonly userId: string;\n\n constructor(\n entitlementResult: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n resourceId: string,\n userId: string,\n ) {\n // Create a user-friendly message based on the failure reason\n const message = BlimuForbiddenException.buildMessage(entitlementResult, entitlementKey);\n\n super({\n message,\n entitlementResult,\n entitlementKey,\n resourceId,\n userId,\n });\n\n this.entitlementResult = entitlementResult;\n this.entitlementKey = entitlementKey;\n this.resourceId = resourceId;\n this.userId = userId;\n }\n\n /**\n * Builds a user-friendly error message from the entitlement check result\n */\n private static buildMessage(\n result: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n ): string {\n const reasons: string[] = [];\n\n if (result.roles && !result.roles.allowed) {\n reasons.push(\n `Insufficient roles. Required: ${result.roles.allowedRoles?.join(', ') || 'unknown'}. User has: ${result.roles.userRoles?.join(', ') || 'none'}.`,\n );\n }\n\n if (result.plans && !result.plans.allowed) {\n reasons.push(\n `Plan restriction. Required plans: ${result.plans.allowedPlans?.join(', ') || 'unknown'}. Current plan: ${result.plans.plan || 'none'}.`,\n );\n }\n\n if (result.limit && !result.limit.allowed) {\n reasons.push(`Usage limit exceeded. ${result.limit.reason || 'Limit has been reached'}.`);\n }\n\n if (reasons.length === 0) {\n return `Access denied for entitlement: ${entitlementKey}`;\n }\n\n return `Access denied for entitlement \"${entitlementKey}\": ${reasons.join(' ')}`;\n }\n}\n","import { Inject, Injectable, Logger } from '@nestjs/common';\nimport { TokenVerifier } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\n@Injectable()\nexport class JWKService {\n private readonly logger = new Logger(JWKService.name);\n private readonly tokenVerifier: TokenVerifier;\n\n constructor(@Inject(BLIMU_CONFIG) private readonly config: BlimuConfig) {\n this.tokenVerifier = new TokenVerifier({\n runtimeApiUrl: this.config.baseURL,\n });\n }\n\n /**\n * Verify JWT token using JWKs from runtime-api\n */\n async verifyToken<T = unknown>(token: string): Promise<T> {\n try {\n this.logger.debug(\n `🔍 Verifying token. Runtime API URL: ${this.config.baseURL}, API Key prefix: ${this.config.apiKey?.substring(0, 10)}...`,\n );\n\n const result = await this.tokenVerifier.verifyToken<T>({\n secretKey: this.config.apiKey,\n token,\n runtimeApiUrl: this.config.baseURL,\n });\n\n this.logger.debug(`✅ Token verified successfully`);\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(`❌ Token verification failed: ${errorMessage}`);\n if (error instanceof Error && error.stack) {\n this.logger.error(`Stack trace: ${error.stack}`);\n }\n throw error;\n }\n }\n\n /**\n * Clear cache (useful for testing or key rotation)\n */\n clearCache(): void {\n this.tokenVerifier.clearCache(this.config.apiKey);\n this.logger.debug('JWK cache cleared');\n }\n}\n","import { applyDecorators, UseGuards } from '@nestjs/common';\nimport {\n EntitlementGuard,\n SetEntitlementMetadata,\n type EntitlementCtx,\n} from '../guards/entitlement.guard';\nimport type { EntitlementType } from '@blimu/types';\n\n/**\n * Decorator to check if the authenticated user has a specific entitlement on a resource.\n *\n * This decorator combines the entitlement metadata setting and guard application\n * to provide a clean, declarative way to protect routes with entitlement checks.\n *\n * @param entitlementKey - The entitlement key to check (e.g., 'brand:read', 'organization:create_workspace')\n * @param entitlementCtxResolver - Optional function that returns entitlement context including resourceId and optionally amount for usage limits.\n * If not provided, the default resolver from module configuration will be used.\n *\n * @example\n * Basic usage with path parameter:\n * ```typescript\n * @Get('/:resourceId')\n * @Entitlement('brand:read', (req) => ({ resourceId: req.params.resourceId }))\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n *\n * @example\n * Using default resolver from module configuration:\n * ```typescript\n * // In module configuration:\n * BlimuModule.forRoot({\n * // ... other config\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * })\n *\n * // In controller (no resolver needed):\n * @Get('/:resourceId')\n * @Entitlement('brand:read')\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // Uses default resolver from config\n * }\n * ```\n *\n * @example\n * Using with typed parameters:\n * ```typescript\n * @Get('/:resourceType/:resourceId')\n * @Entitlement('workspace:delete', (req) => ({ resourceId: req.params.resourceId }))\n * async deleteResource(@Param() params: ResourceParamsDto) {\n * // User is guaranteed to have 'workspace:delete' entitlement\n * }\n * ```\n *\n * @example\n * Complex resource ID extraction:\n * ```typescript\n * @Post('/organizations/:orgId/workspaces')\n * @Entitlement('organization:create_workspace', (req) => {\n * const params = req.params as { orgId: string };\n * return { resourceId: params.orgId };\n * })\n * async createWorkspace(@Param() params: CreateWorkspaceParamsDto, @Body() body: CreateWorkspaceDto) {\n * // User is guaranteed to have 'organization:create_workspace' entitlement on the organization\n * }\n * ```\n *\n * @example\n * With usage limit consumption:\n * ```typescript\n * @Post('/api-calls')\n * @Entitlement('organization:make_api_call', (req) => ({\n * resourceId: req.params.orgId,\n * amount: req.body.apiCallsCount, // Amount to consume from usage limit\n * }))\n * async makeApiCalls(@Param('orgId') orgId: string, @Body() body: { apiCallsCount: number }) {\n * // User is guaranteed to have 'organization:make_api_call' entitlement\n * // and sufficient usage limit balance\n * }\n * ```\n *\n * @example\n * Async resource context extraction (e.g., from database):\n * ```typescript\n * @Delete('/items/:itemId')\n * @Entitlement('workspace:delete_item', async (req) => {\n * // You could fetch the workspace ID from your database\n * const item = await itemService.findById(req.params.itemId);\n * return { resourceId: item.workspaceId };\n * })\n * async deleteItem(@Param('itemId') itemId: string) {\n * // User is guaranteed to have 'workspace:delete_item' entitlement on the item's workspace\n * }\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Get('/:resourceId')\n * @Entitlement<AuthenticatedRequest>('brand:read', (req) => {\n * // req is typed as AuthenticatedRequest, so req.user is properly typed\n * console.log(req.user.email); // TypeScript knows this exists\n * return { resourceId: req.params.resourceId };\n * })\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const Entitlement = <TRequest = any>(\n entitlementKey: EntitlementType,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator => {\n return applyDecorators(\n SetEntitlementMetadata<TRequest>(entitlementKey, entitlementCtxResolver),\n UseGuards(EntitlementGuard),\n );\n};\n"],"mappings":"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","names":["import_common","import_common","import_common","import_backend","import_backend","config","module","import_common"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/modules/blimu.module.ts","../src/config/blimu.config.ts","../src/guards/entitlement.guard.ts","../src/exceptions/blimu-forbidden.exception.ts","../src/services/jwk.service.ts","../src/decorators/entitlement.decorator.ts"],"sourcesContent":["// Main module\nexport * from './modules/blimu.module';\n\n// Configuration\nexport * from './config/blimu.config';\n\n// Guards\nexport * from './guards/entitlement.guard';\n\n// Decorators\nexport * from './decorators/entitlement.decorator';\n\n// Exceptions\nexport * from './exceptions/blimu-forbidden.exception';\n\n// Services\nexport * from './services';\n","import {\n Module,\n type DynamicModule,\n type Type,\n type ForwardReference,\n type InjectionToken,\n type OptionalFactoryDependency,\n} from '@nestjs/common';\n\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\nimport { EntitlementGuard } from '../guards/entitlement.guard';\nimport { JWKService } from '../services/jwk.service';\nimport { Blimu } from '@blimu/backend';\n\nconst DEFAULT_BASE_URL = 'https://api.blimu.dev';\n\n/**\n * Blimu NestJS Module\n *\n * This module provides entitlement checking capabilities and Blimu Runtime SDK integration\n * for NestJS applications. It can be configured synchronously or asynchronously.\n */\n@Module({})\nexport class BlimuModule {\n /**\n * Configure the Blimu module with static configuration\n *\n * @param config - The Blimu configuration object\n * @returns A configured dynamic module\n *\n * @example\n * Basic usage with default Request type:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRoot({\n * apiKey: 'your-api-secret-key',\n * baseURL: 'https://api.blimu.dev', // optional\n * environmentId: 'your-environment-id', // optional\n * timeoutMs: 30000, // optional\n * getUserId: (req) => req.user?.id, // required\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }), // optional\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Usage with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRoot<AuthenticatedRequest>({\n * apiKey: 'your-api-secret-key',\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRoot<TRequest = unknown>(config: BlimuConfig<TRequest>): DynamicModule {\n return {\n ...(config.global ? { global: true } : {}),\n module: BlimuModule,\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useValue: {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n },\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n }\n\n /**\n * Configure the Blimu module with async configuration\n *\n * This is useful when you need to load configuration from environment variables,\n * configuration services, or other async sources.\n *\n * @param options - Async configuration options\n * @returns A configured dynamic module\n *\n * @example\n * Using with ConfigService:\n * ```typescript\n * @Module({\n * imports: [\n * ConfigModule.forRoot(),\n * BlimuModule.forRootAsync({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * baseURL: configService.get('BLIMU_BASE_URL'),\n * environmentId: configService.get('BLIMU_ENVIRONMENT_ID'),\n * timeoutMs: configService.get('BLIMU_TIMEOUT_MS'),\n * getUserId: (req) => req.user?.id,\n * defaultEntitlementCtxResolver: (entitlementKey, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync<AuthenticatedRequest>({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom provider:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync({\n * imports: [MyConfigModule],\n * useFactory: async (myConfigService: MyConfigService) => {\n * const config = await myConfigService.getBlimuConfig();\n * return {\n * apiKey: config.apiKey,\n * baseURL: config.baseUrl,\n * environmentId: config.environmentId,\n * getUserId: (req) => req.user?.id,\n * };\n * },\n * inject: [MyConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync<TRequest = unknown>(options: {\n global?: boolean | undefined;\n useFactory: (...args: unknown[]) => Promise<BlimuConfig<TRequest>> | BlimuConfig<TRequest>;\n inject?: (InjectionToken | OptionalFactoryDependency)[];\n imports?: (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference<() => Type<unknown>>\n )[];\n }): DynamicModule {\n const additionalImports = options.imports ?? [];\n\n const module = {\n ...(options.global ? { global: true } : {}),\n module: BlimuModule,\n imports: [...additionalImports] as (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference\n )[],\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useFactory: async (...args: unknown[]) => {\n const configResult = options.useFactory(...args);\n const config = configResult instanceof Promise ? await configResult : configResult;\n return {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n };\n },\n ...(options.inject ? { inject: options.inject } : {}),\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n return module;\n }\n}\n","import type { EntitlementType, ResourceType } from '@blimu/types';\n/**\n * Configuration interface for Blimu NestJS integration\n */\nexport interface BlimuConfig<TRequest = unknown> {\n global?: boolean | undefined;\n /**\n * The API secret key for authenticating with Blimu Runtime API\n */\n apiKey: string;\n\n /**\n * The base URL for the Blimu Runtime API\n * @default 'https://api.blimu.dev'\n */\n baseURL?: string | undefined;\n\n /**\n * Environment ID for the Blimu environment\n * This will be used in future versions for environment-specific configurations\n */\n environmentId?: string | undefined;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeoutMs?: number | undefined;\n\n /**\n * Function to extract user ID from the request\n *\n * This function is called by the EntitlementGuard to determine which user\n * to check entitlements for. It should return the user ID as a string.\n *\n * @param request - The incoming HTTP request\n * @returns The user ID as a string, or a Promise that resolves to the user ID\n *\n * @example\n * ```typescript\n * // Extract from JWT token in Authorization header\n * getUserId: (req) => {\n * const token = req.headers.authorization?.replace('Bearer ', '');\n * const decoded = jwt.verify(token, secret);\n * return decoded.sub;\n * }\n *\n * // Extract from request.user (common with Passport.js)\n * getUserId: (req) => req.user?.id\n *\n * // Extract from custom header\n * getUserId: (req) => req.headers['x-user-id']\n * ```\n */\n getUserId: (request: TRequest) => string | Promise<string>;\n\n /**\n * Optional default entitlement context resolver for entitlement checks\n *\n * This function is used as a fallback when a decorator-specific resolver is not provided.\n * It receives an object with the entitlement key and parsed resource type, along with the request,\n * allowing for context-aware resolution.\n *\n * @param context - Object containing the entitlement and resource type (parsed from entitlement by splitting on ':')\n * @param request - The incoming HTTP request\n * @returns Entitlement context with resourceId and optionally amount, or a Promise that resolves to it\n *\n * @example\n * ```typescript\n * // Extract resourceId from path parameter\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * })\n *\n * // Context-aware resolution based on resource type\n * defaultEntitlementCtxResolver: ({ resourceType }, req) => {\n * if (resourceType === 'organization') {\n * return { resourceId: req.params.orgId };\n * }\n * return { resourceId: req.params.resourceId };\n * }\n *\n * // Extract from request body or query with amount for consumption\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.body?.resourceId || req.query?.resourceId,\n * amount: req.body?.amount, // Amount to consume for usage-based entitlements\n * })\n * ```\n */\n defaultEntitlementCtxResolver?: (\n context: { entitlement: EntitlementType; resourceType: ResourceType },\n request: TRequest,\n ) =>\n | { resourceId: string; amount?: number }\n | Promise<{ resourceId: string; amount?: number }>\n | undefined;\n}\n\n/**\n * Injection token for Blimu configuration\n */\nexport const BLIMU_CONFIG = Symbol('BLIMU_CONFIG');\n","import {\n type CanActivate,\n type ExecutionContext,\n ForbiddenException,\n Injectable,\n SetMetadata,\n Inject,\n} from '@nestjs/common';\nimport 'reflect-metadata';\n\nimport type { EntitlementType } from '@blimu/types';\nimport { BlimuForbiddenException } from '../exceptions/blimu-forbidden.exception';\nimport { Blimu } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\nexport const ENTITLEMENT_KEY = 'entitlement';\nexport const ENTITLEMENT_METADATA_KEY = Symbol('entitlement');\n\n/**\n * Entitlement context returned by the entitlementCtxResolver callback\n */\nexport interface EntitlementCtx {\n resourceId: string;\n amount?: number; // Amount to check against usage limit (for consumption)\n}\n\n/**\n * Metadata interface for entitlement checks\n */\nexport interface EntitlementMetadata<TRequest = unknown> {\n entitlementKey: EntitlementType;\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>;\n}\n\n/**\n * Sets entitlement metadata for a route handler\n * @internal This is used internally by the @Entitlement decorator\n */\nexport const SetEntitlementMetadata = <TRequest = unknown>(\n entitlementKey: string,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator =>\n SetMetadata(ENTITLEMENT_METADATA_KEY, {\n entitlementKey,\n entitlementCtxResolver,\n } as EntitlementMetadata<TRequest>);\n\n/**\n * Guard that checks if the authenticated user has the required entitlement on a resource\n *\n * This guard automatically:\n * 1. Extracts the user from the request\n * 2. Extracts the resource ID using the provided extractor function\n * 3. Calls the Blimu Runtime API to check entitlements\n * 4. Allows or denies access based on the result\n */\n@Injectable()\nexport class EntitlementGuard<TRequest = unknown> implements CanActivate {\n constructor(\n @Inject(BLIMU_CONFIG)\n private readonly config: BlimuConfig<TRequest>,\n @Inject(Blimu)\n private readonly runtime: Blimu,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<TRequest>();\n const handler = context.getHandler();\n const metadata = Reflect.getMetadata(ENTITLEMENT_METADATA_KEY, handler) as\n | EntitlementMetadata<TRequest>\n | undefined;\n\n if (!metadata) {\n // No entitlement check required\n return true;\n }\n\n // Extract user ID using the configured getUserId function\n let userId: string;\n try {\n userId = await this.config.getUserId(request);\n } catch {\n throw new ForbiddenException('Failed to extract user ID from request');\n }\n\n if (!userId) {\n throw new ForbiddenException('User ID is required for entitlement check');\n }\n\n // Resolve entitlement context from request\n // Priority: decorator resolver > default resolver > error\n let entitlementCtx: EntitlementCtx | undefined;\n if (metadata.entitlementCtxResolver) {\n entitlementCtx = await metadata.entitlementCtxResolver(request);\n } else if (this.config.defaultEntitlementCtxResolver) {\n // Parse resourceType from entitlementKey (format: \"resourceType:action\")\n const resourceType = metadata.entitlementKey.split(':')[0] || '';\n entitlementCtx = await this.config.defaultEntitlementCtxResolver(\n {\n entitlement: metadata.entitlementKey,\n resourceType,\n },\n request,\n );\n } else {\n throw new ForbiddenException('No entitlement context resolver available');\n }\n\n if (!entitlementCtx?.resourceId) {\n throw new ForbiddenException('Resource ID is required for entitlement check');\n }\n\n try {\n // Check entitlement\n const result = await this.runtime.entitlements.checkEntitlement({\n userId,\n entitlement: metadata.entitlementKey,\n resourceId: entitlementCtx.resourceId,\n ...(entitlementCtx.amount !== undefined ? { amount: entitlementCtx.amount } : {}),\n });\n\n if (!result.allowed) {\n throw new BlimuForbiddenException(\n result,\n metadata.entitlementKey,\n entitlementCtx.resourceId,\n userId,\n );\n }\n\n return true;\n } catch (error) {\n if (error instanceof BlimuForbiddenException || error instanceof ForbiddenException) {\n throw error;\n }\n\n // Log the error for debugging but don't expose internal details\n console.error('Entitlement check failed:', error);\n throw new ForbiddenException('Failed to verify entitlements');\n }\n }\n}\n","import { ForbiddenException } from '@nestjs/common';\nimport type { Schema } from '@blimu/backend';\nimport type { EntitlementType } from '@blimu/types';\n/**\n * Custom exception for Blimu entitlement check failures\n *\n * This exception extends NestJS's ForbiddenException and includes\n * the typed EntitlementCheckResult, providing detailed information\n * about why the entitlement check failed (roles, plans, limits, etc.)\n */\nexport class BlimuForbiddenException extends ForbiddenException {\n /**\n * The entitlement check result containing detailed failure information\n */\n public readonly entitlementResult: Schema.EntitlementCheckResult;\n\n /**\n * The entitlement key that was checked\n */\n public readonly entitlementKey: EntitlementType;\n\n /**\n * The resource ID that was checked\n */\n public readonly resourceId: string;\n\n /**\n * The user ID that was checked\n */\n public readonly userId: string;\n\n constructor(\n entitlementResult: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n resourceId: string,\n userId: string,\n ) {\n // Create a user-friendly message based on the failure reason\n const message = BlimuForbiddenException.buildMessage(entitlementResult, entitlementKey);\n\n super({\n message,\n entitlementResult,\n entitlementKey,\n resourceId,\n userId,\n });\n\n this.entitlementResult = entitlementResult;\n this.entitlementKey = entitlementKey;\n this.resourceId = resourceId;\n this.userId = userId;\n }\n\n /**\n * Builds a user-friendly error message from the entitlement check result\n */\n private static buildMessage(\n result: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n ): string {\n const reasons: string[] = [];\n\n if (result.roles && !result.roles.allowed) {\n reasons.push(\n `Insufficient roles. Required: ${result.roles.allowedRoles?.join(', ') || 'unknown'}. User has: ${result.roles.userRoles?.join(', ') || 'none'}.`,\n );\n }\n\n if (result.plans && !result.plans.allowed) {\n reasons.push(\n `Plan restriction. Required plans: ${result.plans.allowedPlans?.join(', ') || 'unknown'}. Current plan: ${result.plans.plan || 'none'}.`,\n );\n }\n\n if (result.limit && !result.limit.allowed) {\n reasons.push(`Usage limit exceeded. ${result.limit.reason || 'Limit has been reached'}.`);\n }\n\n if (reasons.length === 0) {\n return `Access denied for entitlement: ${entitlementKey}`;\n }\n\n return `Access denied for entitlement \"${entitlementKey}\": ${reasons.join(' ')}`;\n }\n}\n","import { Inject, Injectable, Logger } from '@nestjs/common';\nimport { TokenVerifier } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\n@Injectable()\nexport class JWKService {\n private readonly logger = new Logger(JWKService.name);\n private readonly tokenVerifier: TokenVerifier;\n\n constructor(@Inject(BLIMU_CONFIG) private readonly config: BlimuConfig) {\n this.tokenVerifier = new TokenVerifier({\n runtimeApiUrl: this.config.baseURL,\n });\n }\n\n /**\n * Verify JWT token issued by Blimu's main auth (environment/session tokens).\n * Uses the configured API key and environment JWKS.\n */\n async verifyToken<T = unknown>(token: string): Promise<T> {\n try {\n this.logger.debug(\n `🔍 Verifying token. Runtime API URL: ${this.config.baseURL}, API Key prefix: ${this.config.apiKey?.substring(0, 10)}...`,\n );\n\n const result = await this.tokenVerifier.verifyToken<T>({\n secretKey: this.config.apiKey,\n token,\n runtimeApiUrl: this.config.baseURL,\n });\n\n this.logger.debug(`✅ Token verified successfully`);\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(`❌ Token verification failed: ${errorMessage}`);\n if (error instanceof Error && error.stack) {\n this.logger.error(`Stack trace: ${error.stack}`);\n }\n throw error;\n }\n }\n\n /**\n * Verify JWT access token issued by an OAuth2 app (e.g. device code or authorization code flow).\n * Uses the public OAuth JWKS endpoint; no API key required. Pass the OAuth app's client_id.\n */\n async verifyOAuthToken<T = unknown>(token: string, clientId: string): Promise<T> {\n try {\n this.logger.debug(\n `🔍 Verifying OAuth token. Runtime API URL: ${this.config.baseURL}, clientId: ${clientId}`,\n );\n\n const result = await this.tokenVerifier.verifyToken<T>({\n clientId,\n token,\n runtimeApiUrl: this.config.baseURL,\n });\n\n this.logger.debug(`✅ OAuth token verified successfully`);\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(`❌ OAuth token verification failed: ${errorMessage}`);\n if (error instanceof Error && error.stack) {\n this.logger.error(`Stack trace: ${error.stack}`);\n }\n throw error;\n }\n }\n\n /**\n * Clear cache (useful for testing or key rotation)\n */\n clearCache(): void {\n this.tokenVerifier.clearCache(this.config.apiKey);\n this.logger.debug('JWK cache cleared');\n }\n}\n","import { applyDecorators, UseGuards } from '@nestjs/common';\nimport {\n EntitlementGuard,\n SetEntitlementMetadata,\n type EntitlementCtx,\n} from '../guards/entitlement.guard';\nimport type { EntitlementType } from '@blimu/types';\n\n/**\n * Decorator to check if the authenticated user has a specific entitlement on a resource.\n *\n * This decorator combines the entitlement metadata setting and guard application\n * to provide a clean, declarative way to protect routes with entitlement checks.\n *\n * @param entitlementKey - The entitlement key to check (e.g., 'brand:read', 'organization:create_workspace')\n * @param entitlementCtxResolver - Optional function that returns entitlement context including resourceId and optionally amount for usage limits.\n * If not provided, the default resolver from module configuration will be used.\n *\n * @example\n * Basic usage with path parameter:\n * ```typescript\n * @Get('/:resourceId')\n * @Entitlement('brand:read', (req) => ({ resourceId: req.params.resourceId }))\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n *\n * @example\n * Using default resolver from module configuration:\n * ```typescript\n * // In module configuration:\n * BlimuModule.forRoot({\n * // ... other config\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * })\n *\n * // In controller (no resolver needed):\n * @Get('/:resourceId')\n * @Entitlement('brand:read')\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // Uses default resolver from config\n * }\n * ```\n *\n * @example\n * Using with typed parameters:\n * ```typescript\n * @Get('/:resourceType/:resourceId')\n * @Entitlement('workspace:delete', (req) => ({ resourceId: req.params.resourceId }))\n * async deleteResource(@Param() params: ResourceParamsDto) {\n * // User is guaranteed to have 'workspace:delete' entitlement\n * }\n * ```\n *\n * @example\n * Complex resource ID extraction:\n * ```typescript\n * @Post('/organizations/:orgId/workspaces')\n * @Entitlement('organization:create_workspace', (req) => {\n * const params = req.params as { orgId: string };\n * return { resourceId: params.orgId };\n * })\n * async createWorkspace(@Param() params: CreateWorkspaceParamsDto, @Body() body: CreateWorkspaceDto) {\n * // User is guaranteed to have 'organization:create_workspace' entitlement on the organization\n * }\n * ```\n *\n * @example\n * With usage limit consumption:\n * ```typescript\n * @Post('/api-calls')\n * @Entitlement('organization:make_api_call', (req) => ({\n * resourceId: req.params.orgId,\n * amount: req.body.apiCallsCount, // Amount to consume from usage limit\n * }))\n * async makeApiCalls(@Param('orgId') orgId: string, @Body() body: { apiCallsCount: number }) {\n * // User is guaranteed to have 'organization:make_api_call' entitlement\n * // and sufficient usage limit balance\n * }\n * ```\n *\n * @example\n * Async resource context extraction (e.g., from database):\n * ```typescript\n * @Delete('/items/:itemId')\n * @Entitlement('workspace:delete_item', async (req) => {\n * // You could fetch the workspace ID from your database\n * const item = await itemService.findById(req.params.itemId);\n * return { resourceId: item.workspaceId };\n * })\n * async deleteItem(@Param('itemId') itemId: string) {\n * // User is guaranteed to have 'workspace:delete_item' entitlement on the item's workspace\n * }\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Get('/:resourceId')\n * @Entitlement<AuthenticatedRequest>('brand:read', (req) => {\n * // req is typed as AuthenticatedRequest, so req.user is properly typed\n * console.log(req.user.email); // TypeScript knows this exists\n * return { resourceId: req.params.resourceId };\n * })\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const Entitlement = <TRequest = any>(\n entitlementKey: EntitlementType,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator => {\n return applyDecorators(\n SetEntitlementMetadata<TRequest>(entitlementKey, entitlementCtxResolver),\n UseGuards(EntitlementGuard),\n );\n};\n"],"mappings":"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","names":["import_common","import_common","import_common","import_backend","import_backend","config","module","import_common"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -67,6 +67,7 @@ declare class JWKService {
|
|
|
67
67
|
private readonly tokenVerifier;
|
|
68
68
|
constructor(config: BlimuConfig);
|
|
69
69
|
verifyToken<T = unknown>(token: string): Promise<T>;
|
|
70
|
+
verifyOAuthToken<T = unknown>(token: string, clientId: string): Promise<T>;
|
|
70
71
|
clearCache(): void;
|
|
71
72
|
}
|
|
72
73
|
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ declare class JWKService {
|
|
|
67
67
|
private readonly tokenVerifier;
|
|
68
68
|
constructor(config: BlimuConfig);
|
|
69
69
|
verifyToken<T = unknown>(token: string): Promise<T>;
|
|
70
|
+
verifyOAuthToken<T = unknown>(token: string, clientId: string): Promise<T>;
|
|
70
71
|
clearCache(): void;
|
|
71
72
|
}
|
|
72
73
|
|
package/dist/index.mjs
CHANGED
|
@@ -176,7 +176,8 @@ var JWKService = class {
|
|
|
176
176
|
logger = new Logger(JWKService.name);
|
|
177
177
|
tokenVerifier;
|
|
178
178
|
/**
|
|
179
|
-
* Verify JWT token
|
|
179
|
+
* Verify JWT token issued by Blimu's main auth (environment/session tokens).
|
|
180
|
+
* Uses the configured API key and environment JWKS.
|
|
180
181
|
*/
|
|
181
182
|
async verifyToken(token) {
|
|
182
183
|
try {
|
|
@@ -199,6 +200,31 @@ var JWKService = class {
|
|
|
199
200
|
throw error;
|
|
200
201
|
}
|
|
201
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Verify JWT access token issued by an OAuth2 app (e.g. device code or authorization code flow).
|
|
205
|
+
* Uses the public OAuth JWKS endpoint; no API key required. Pass the OAuth app's client_id.
|
|
206
|
+
*/
|
|
207
|
+
async verifyOAuthToken(token, clientId) {
|
|
208
|
+
try {
|
|
209
|
+
this.logger.debug(
|
|
210
|
+
`\u{1F50D} Verifying OAuth token. Runtime API URL: ${this.config.baseURL}, clientId: ${clientId}`
|
|
211
|
+
);
|
|
212
|
+
const result = await this.tokenVerifier.verifyToken({
|
|
213
|
+
clientId,
|
|
214
|
+
token,
|
|
215
|
+
runtimeApiUrl: this.config.baseURL
|
|
216
|
+
});
|
|
217
|
+
this.logger.debug(`\u2705 OAuth token verified successfully`);
|
|
218
|
+
return result;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
221
|
+
this.logger.error(`\u274C OAuth token verification failed: ${errorMessage}`);
|
|
222
|
+
if (error instanceof Error && error.stack) {
|
|
223
|
+
this.logger.error(`Stack trace: ${error.stack}`);
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
202
228
|
/**
|
|
203
229
|
* Clear cache (useful for testing or key rotation)
|
|
204
230
|
*/
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/modules/blimu.module.ts","../src/config/blimu.config.ts","../src/guards/entitlement.guard.ts","../src/exceptions/blimu-forbidden.exception.ts","../src/services/jwk.service.ts","../src/decorators/entitlement.decorator.ts"],"sourcesContent":["import {\n Module,\n type DynamicModule,\n type Type,\n type ForwardReference,\n type InjectionToken,\n type OptionalFactoryDependency,\n} from '@nestjs/common';\n\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\nimport { EntitlementGuard } from '../guards/entitlement.guard';\nimport { JWKService } from '../services/jwk.service';\nimport { Blimu } from '@blimu/backend';\n\nconst DEFAULT_BASE_URL = 'https://api.blimu.dev';\n\n/**\n * Blimu NestJS Module\n *\n * This module provides entitlement checking capabilities and Blimu Runtime SDK integration\n * for NestJS applications. It can be configured synchronously or asynchronously.\n */\n@Module({})\nexport class BlimuModule {\n /**\n * Configure the Blimu module with static configuration\n *\n * @param config - The Blimu configuration object\n * @returns A configured dynamic module\n *\n * @example\n * Basic usage with default Request type:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRoot({\n * apiKey: 'your-api-secret-key',\n * baseURL: 'https://api.blimu.dev', // optional\n * environmentId: 'your-environment-id', // optional\n * timeoutMs: 30000, // optional\n * getUserId: (req) => req.user?.id, // required\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }), // optional\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Usage with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRoot<AuthenticatedRequest>({\n * apiKey: 'your-api-secret-key',\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRoot<TRequest = unknown>(config: BlimuConfig<TRequest>): DynamicModule {\n return {\n ...(config.global ? { global: true } : {}),\n module: BlimuModule,\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useValue: {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n },\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n }\n\n /**\n * Configure the Blimu module with async configuration\n *\n * This is useful when you need to load configuration from environment variables,\n * configuration services, or other async sources.\n *\n * @param options - Async configuration options\n * @returns A configured dynamic module\n *\n * @example\n * Using with ConfigService:\n * ```typescript\n * @Module({\n * imports: [\n * ConfigModule.forRoot(),\n * BlimuModule.forRootAsync({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * baseURL: configService.get('BLIMU_BASE_URL'),\n * environmentId: configService.get('BLIMU_ENVIRONMENT_ID'),\n * timeoutMs: configService.get('BLIMU_TIMEOUT_MS'),\n * getUserId: (req) => req.user?.id,\n * defaultEntitlementCtxResolver: (entitlementKey, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync<AuthenticatedRequest>({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom provider:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync({\n * imports: [MyConfigModule],\n * useFactory: async (myConfigService: MyConfigService) => {\n * const config = await myConfigService.getBlimuConfig();\n * return {\n * apiKey: config.apiKey,\n * baseURL: config.baseUrl,\n * environmentId: config.environmentId,\n * getUserId: (req) => req.user?.id,\n * };\n * },\n * inject: [MyConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync<TRequest = unknown>(options: {\n global?: boolean | undefined;\n useFactory: (...args: unknown[]) => Promise<BlimuConfig<TRequest>> | BlimuConfig<TRequest>;\n inject?: (InjectionToken | OptionalFactoryDependency)[];\n imports?: (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference<() => Type<unknown>>\n )[];\n }): DynamicModule {\n const additionalImports = options.imports ?? [];\n\n const module = {\n ...(options.global ? { global: true } : {}),\n module: BlimuModule,\n imports: [...additionalImports] as (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference\n )[],\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useFactory: async (...args: unknown[]) => {\n const configResult = options.useFactory(...args);\n const config = configResult instanceof Promise ? await configResult : configResult;\n return {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n };\n },\n ...(options.inject ? { inject: options.inject } : {}),\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n return module;\n }\n}\n","import type { EntitlementType, ResourceType } from '@blimu/types';\n/**\n * Configuration interface for Blimu NestJS integration\n */\nexport interface BlimuConfig<TRequest = unknown> {\n global?: boolean | undefined;\n /**\n * The API secret key for authenticating with Blimu Runtime API\n */\n apiKey: string;\n\n /**\n * The base URL for the Blimu Runtime API\n * @default 'https://api.blimu.dev'\n */\n baseURL?: string | undefined;\n\n /**\n * Environment ID for the Blimu environment\n * This will be used in future versions for environment-specific configurations\n */\n environmentId?: string | undefined;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeoutMs?: number | undefined;\n\n /**\n * Function to extract user ID from the request\n *\n * This function is called by the EntitlementGuard to determine which user\n * to check entitlements for. It should return the user ID as a string.\n *\n * @param request - The incoming HTTP request\n * @returns The user ID as a string, or a Promise that resolves to the user ID\n *\n * @example\n * ```typescript\n * // Extract from JWT token in Authorization header\n * getUserId: (req) => {\n * const token = req.headers.authorization?.replace('Bearer ', '');\n * const decoded = jwt.verify(token, secret);\n * return decoded.sub;\n * }\n *\n * // Extract from request.user (common with Passport.js)\n * getUserId: (req) => req.user?.id\n *\n * // Extract from custom header\n * getUserId: (req) => req.headers['x-user-id']\n * ```\n */\n getUserId: (request: TRequest) => string | Promise<string>;\n\n /**\n * Optional default entitlement context resolver for entitlement checks\n *\n * This function is used as a fallback when a decorator-specific resolver is not provided.\n * It receives an object with the entitlement key and parsed resource type, along with the request,\n * allowing for context-aware resolution.\n *\n * @param context - Object containing the entitlement and resource type (parsed from entitlement by splitting on ':')\n * @param request - The incoming HTTP request\n * @returns Entitlement context with resourceId and optionally amount, or a Promise that resolves to it\n *\n * @example\n * ```typescript\n * // Extract resourceId from path parameter\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * })\n *\n * // Context-aware resolution based on resource type\n * defaultEntitlementCtxResolver: ({ resourceType }, req) => {\n * if (resourceType === 'organization') {\n * return { resourceId: req.params.orgId };\n * }\n * return { resourceId: req.params.resourceId };\n * }\n *\n * // Extract from request body or query with amount for consumption\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.body?.resourceId || req.query?.resourceId,\n * amount: req.body?.amount, // Amount to consume for usage-based entitlements\n * })\n * ```\n */\n defaultEntitlementCtxResolver?: (\n context: { entitlement: EntitlementType; resourceType: ResourceType },\n request: TRequest,\n ) =>\n | { resourceId: string; amount?: number }\n | Promise<{ resourceId: string; amount?: number }>\n | undefined;\n}\n\n/**\n * Injection token for Blimu configuration\n */\nexport const BLIMU_CONFIG = Symbol('BLIMU_CONFIG');\n","import {\n type CanActivate,\n type ExecutionContext,\n ForbiddenException,\n Injectable,\n SetMetadata,\n Inject,\n} from '@nestjs/common';\nimport 'reflect-metadata';\n\nimport type { EntitlementType } from '@blimu/types';\nimport { BlimuForbiddenException } from '../exceptions/blimu-forbidden.exception';\nimport { Blimu } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\nexport const ENTITLEMENT_KEY = 'entitlement';\nexport const ENTITLEMENT_METADATA_KEY = Symbol('entitlement');\n\n/**\n * Entitlement context returned by the entitlementCtxResolver callback\n */\nexport interface EntitlementCtx {\n resourceId: string;\n amount?: number; // Amount to check against usage limit (for consumption)\n}\n\n/**\n * Metadata interface for entitlement checks\n */\nexport interface EntitlementMetadata<TRequest = unknown> {\n entitlementKey: EntitlementType;\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>;\n}\n\n/**\n * Sets entitlement metadata for a route handler\n * @internal This is used internally by the @Entitlement decorator\n */\nexport const SetEntitlementMetadata = <TRequest = unknown>(\n entitlementKey: string,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator =>\n SetMetadata(ENTITLEMENT_METADATA_KEY, {\n entitlementKey,\n entitlementCtxResolver,\n } as EntitlementMetadata<TRequest>);\n\n/**\n * Guard that checks if the authenticated user has the required entitlement on a resource\n *\n * This guard automatically:\n * 1. Extracts the user from the request\n * 2. Extracts the resource ID using the provided extractor function\n * 3. Calls the Blimu Runtime API to check entitlements\n * 4. Allows or denies access based on the result\n */\n@Injectable()\nexport class EntitlementGuard<TRequest = unknown> implements CanActivate {\n constructor(\n @Inject(BLIMU_CONFIG)\n private readonly config: BlimuConfig<TRequest>,\n @Inject(Blimu)\n private readonly runtime: Blimu,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<TRequest>();\n const handler = context.getHandler();\n const metadata = Reflect.getMetadata(ENTITLEMENT_METADATA_KEY, handler) as\n | EntitlementMetadata<TRequest>\n | undefined;\n\n if (!metadata) {\n // No entitlement check required\n return true;\n }\n\n // Extract user ID using the configured getUserId function\n let userId: string;\n try {\n userId = await this.config.getUserId(request);\n } catch {\n throw new ForbiddenException('Failed to extract user ID from request');\n }\n\n if (!userId) {\n throw new ForbiddenException('User ID is required for entitlement check');\n }\n\n // Resolve entitlement context from request\n // Priority: decorator resolver > default resolver > error\n let entitlementCtx: EntitlementCtx | undefined;\n if (metadata.entitlementCtxResolver) {\n entitlementCtx = await metadata.entitlementCtxResolver(request);\n } else if (this.config.defaultEntitlementCtxResolver) {\n // Parse resourceType from entitlementKey (format: \"resourceType:action\")\n const resourceType = metadata.entitlementKey.split(':')[0] || '';\n entitlementCtx = await this.config.defaultEntitlementCtxResolver(\n {\n entitlement: metadata.entitlementKey,\n resourceType,\n },\n request,\n );\n } else {\n throw new ForbiddenException('No entitlement context resolver available');\n }\n\n if (!entitlementCtx?.resourceId) {\n throw new ForbiddenException('Resource ID is required for entitlement check');\n }\n\n try {\n // Check entitlement\n const result = await this.runtime.entitlements.checkEntitlement({\n userId,\n entitlement: metadata.entitlementKey,\n resourceId: entitlementCtx.resourceId,\n ...(entitlementCtx.amount !== undefined ? { amount: entitlementCtx.amount } : {}),\n });\n\n if (!result.allowed) {\n throw new BlimuForbiddenException(\n result,\n metadata.entitlementKey,\n entitlementCtx.resourceId,\n userId,\n );\n }\n\n return true;\n } catch (error) {\n if (error instanceof BlimuForbiddenException || error instanceof ForbiddenException) {\n throw error;\n }\n\n // Log the error for debugging but don't expose internal details\n console.error('Entitlement check failed:', error);\n throw new ForbiddenException('Failed to verify entitlements');\n }\n }\n}\n","import { ForbiddenException } from '@nestjs/common';\nimport type { Schema } from '@blimu/backend';\nimport type { EntitlementType } from '@blimu/types';\n/**\n * Custom exception for Blimu entitlement check failures\n *\n * This exception extends NestJS's ForbiddenException and includes\n * the typed EntitlementCheckResult, providing detailed information\n * about why the entitlement check failed (roles, plans, limits, etc.)\n */\nexport class BlimuForbiddenException extends ForbiddenException {\n /**\n * The entitlement check result containing detailed failure information\n */\n public readonly entitlementResult: Schema.EntitlementCheckResult;\n\n /**\n * The entitlement key that was checked\n */\n public readonly entitlementKey: EntitlementType;\n\n /**\n * The resource ID that was checked\n */\n public readonly resourceId: string;\n\n /**\n * The user ID that was checked\n */\n public readonly userId: string;\n\n constructor(\n entitlementResult: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n resourceId: string,\n userId: string,\n ) {\n // Create a user-friendly message based on the failure reason\n const message = BlimuForbiddenException.buildMessage(entitlementResult, entitlementKey);\n\n super({\n message,\n entitlementResult,\n entitlementKey,\n resourceId,\n userId,\n });\n\n this.entitlementResult = entitlementResult;\n this.entitlementKey = entitlementKey;\n this.resourceId = resourceId;\n this.userId = userId;\n }\n\n /**\n * Builds a user-friendly error message from the entitlement check result\n */\n private static buildMessage(\n result: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n ): string {\n const reasons: string[] = [];\n\n if (result.roles && !result.roles.allowed) {\n reasons.push(\n `Insufficient roles. Required: ${result.roles.allowedRoles?.join(', ') || 'unknown'}. User has: ${result.roles.userRoles?.join(', ') || 'none'}.`,\n );\n }\n\n if (result.plans && !result.plans.allowed) {\n reasons.push(\n `Plan restriction. Required plans: ${result.plans.allowedPlans?.join(', ') || 'unknown'}. Current plan: ${result.plans.plan || 'none'}.`,\n );\n }\n\n if (result.limit && !result.limit.allowed) {\n reasons.push(`Usage limit exceeded. ${result.limit.reason || 'Limit has been reached'}.`);\n }\n\n if (reasons.length === 0) {\n return `Access denied for entitlement: ${entitlementKey}`;\n }\n\n return `Access denied for entitlement \"${entitlementKey}\": ${reasons.join(' ')}`;\n }\n}\n","import { Inject, Injectable, Logger } from '@nestjs/common';\nimport { TokenVerifier } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\n@Injectable()\nexport class JWKService {\n private readonly logger = new Logger(JWKService.name);\n private readonly tokenVerifier: TokenVerifier;\n\n constructor(@Inject(BLIMU_CONFIG) private readonly config: BlimuConfig) {\n this.tokenVerifier = new TokenVerifier({\n runtimeApiUrl: this.config.baseURL,\n });\n }\n\n /**\n * Verify JWT token using JWKs from runtime-api\n */\n async verifyToken<T = unknown>(token: string): Promise<T> {\n try {\n this.logger.debug(\n `🔍 Verifying token. Runtime API URL: ${this.config.baseURL}, API Key prefix: ${this.config.apiKey?.substring(0, 10)}...`,\n );\n\n const result = await this.tokenVerifier.verifyToken<T>({\n secretKey: this.config.apiKey,\n token,\n runtimeApiUrl: this.config.baseURL,\n });\n\n this.logger.debug(`✅ Token verified successfully`);\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(`❌ Token verification failed: ${errorMessage}`);\n if (error instanceof Error && error.stack) {\n this.logger.error(`Stack trace: ${error.stack}`);\n }\n throw error;\n }\n }\n\n /**\n * Clear cache (useful for testing or key rotation)\n */\n clearCache(): void {\n this.tokenVerifier.clearCache(this.config.apiKey);\n this.logger.debug('JWK cache cleared');\n }\n}\n","import { applyDecorators, UseGuards } from '@nestjs/common';\nimport {\n EntitlementGuard,\n SetEntitlementMetadata,\n type EntitlementCtx,\n} from '../guards/entitlement.guard';\nimport type { EntitlementType } from '@blimu/types';\n\n/**\n * Decorator to check if the authenticated user has a specific entitlement on a resource.\n *\n * This decorator combines the entitlement metadata setting and guard application\n * to provide a clean, declarative way to protect routes with entitlement checks.\n *\n * @param entitlementKey - The entitlement key to check (e.g., 'brand:read', 'organization:create_workspace')\n * @param entitlementCtxResolver - Optional function that returns entitlement context including resourceId and optionally amount for usage limits.\n * If not provided, the default resolver from module configuration will be used.\n *\n * @example\n * Basic usage with path parameter:\n * ```typescript\n * @Get('/:resourceId')\n * @Entitlement('brand:read', (req) => ({ resourceId: req.params.resourceId }))\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n *\n * @example\n * Using default resolver from module configuration:\n * ```typescript\n * // In module configuration:\n * BlimuModule.forRoot({\n * // ... other config\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * })\n *\n * // In controller (no resolver needed):\n * @Get('/:resourceId')\n * @Entitlement('brand:read')\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // Uses default resolver from config\n * }\n * ```\n *\n * @example\n * Using with typed parameters:\n * ```typescript\n * @Get('/:resourceType/:resourceId')\n * @Entitlement('workspace:delete', (req) => ({ resourceId: req.params.resourceId }))\n * async deleteResource(@Param() params: ResourceParamsDto) {\n * // User is guaranteed to have 'workspace:delete' entitlement\n * }\n * ```\n *\n * @example\n * Complex resource ID extraction:\n * ```typescript\n * @Post('/organizations/:orgId/workspaces')\n * @Entitlement('organization:create_workspace', (req) => {\n * const params = req.params as { orgId: string };\n * return { resourceId: params.orgId };\n * })\n * async createWorkspace(@Param() params: CreateWorkspaceParamsDto, @Body() body: CreateWorkspaceDto) {\n * // User is guaranteed to have 'organization:create_workspace' entitlement on the organization\n * }\n * ```\n *\n * @example\n * With usage limit consumption:\n * ```typescript\n * @Post('/api-calls')\n * @Entitlement('organization:make_api_call', (req) => ({\n * resourceId: req.params.orgId,\n * amount: req.body.apiCallsCount, // Amount to consume from usage limit\n * }))\n * async makeApiCalls(@Param('orgId') orgId: string, @Body() body: { apiCallsCount: number }) {\n * // User is guaranteed to have 'organization:make_api_call' entitlement\n * // and sufficient usage limit balance\n * }\n * ```\n *\n * @example\n * Async resource context extraction (e.g., from database):\n * ```typescript\n * @Delete('/items/:itemId')\n * @Entitlement('workspace:delete_item', async (req) => {\n * // You could fetch the workspace ID from your database\n * const item = await itemService.findById(req.params.itemId);\n * return { resourceId: item.workspaceId };\n * })\n * async deleteItem(@Param('itemId') itemId: string) {\n * // User is guaranteed to have 'workspace:delete_item' entitlement on the item's workspace\n * }\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Get('/:resourceId')\n * @Entitlement<AuthenticatedRequest>('brand:read', (req) => {\n * // req is typed as AuthenticatedRequest, so req.user is properly typed\n * console.log(req.user.email); // TypeScript knows this exists\n * return { resourceId: req.params.resourceId };\n * })\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const Entitlement = <TRequest = any>(\n entitlementKey: EntitlementType,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator => {\n return applyDecorators(\n SetEntitlementMetadata<TRequest>(entitlementKey, entitlementCtxResolver),\n UseGuards(EntitlementGuard),\n );\n};\n"],"mappings":"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","names":["ForbiddenException","ForbiddenException","Inject","Injectable","Injectable","Inject","Blimu","Blimu","config"]}
|
|
1
|
+
{"version":3,"sources":["../src/modules/blimu.module.ts","../src/config/blimu.config.ts","../src/guards/entitlement.guard.ts","../src/exceptions/blimu-forbidden.exception.ts","../src/services/jwk.service.ts","../src/decorators/entitlement.decorator.ts"],"sourcesContent":["import {\n Module,\n type DynamicModule,\n type Type,\n type ForwardReference,\n type InjectionToken,\n type OptionalFactoryDependency,\n} from '@nestjs/common';\n\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\nimport { EntitlementGuard } from '../guards/entitlement.guard';\nimport { JWKService } from '../services/jwk.service';\nimport { Blimu } from '@blimu/backend';\n\nconst DEFAULT_BASE_URL = 'https://api.blimu.dev';\n\n/**\n * Blimu NestJS Module\n *\n * This module provides entitlement checking capabilities and Blimu Runtime SDK integration\n * for NestJS applications. It can be configured synchronously or asynchronously.\n */\n@Module({})\nexport class BlimuModule {\n /**\n * Configure the Blimu module with static configuration\n *\n * @param config - The Blimu configuration object\n * @returns A configured dynamic module\n *\n * @example\n * Basic usage with default Request type:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRoot({\n * apiKey: 'your-api-secret-key',\n * baseURL: 'https://api.blimu.dev', // optional\n * environmentId: 'your-environment-id', // optional\n * timeoutMs: 30000, // optional\n * getUserId: (req) => req.user?.id, // required\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }), // optional\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Usage with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRoot<AuthenticatedRequest>({\n * apiKey: 'your-api-secret-key',\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRoot<TRequest = unknown>(config: BlimuConfig<TRequest>): DynamicModule {\n return {\n ...(config.global ? { global: true } : {}),\n module: BlimuModule,\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useValue: {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n },\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n }\n\n /**\n * Configure the Blimu module with async configuration\n *\n * This is useful when you need to load configuration from environment variables,\n * configuration services, or other async sources.\n *\n * @param options - Async configuration options\n * @returns A configured dynamic module\n *\n * @example\n * Using with ConfigService:\n * ```typescript\n * @Module({\n * imports: [\n * ConfigModule.forRoot(),\n * BlimuModule.forRootAsync({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * baseURL: configService.get('BLIMU_BASE_URL'),\n * environmentId: configService.get('BLIMU_ENVIRONMENT_ID'),\n * timeoutMs: configService.get('BLIMU_TIMEOUT_MS'),\n * getUserId: (req) => req.user?.id,\n * defaultEntitlementCtxResolver: (entitlementKey, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync<AuthenticatedRequest>({\n * useFactory: (configService: ConfigService) => ({\n * apiKey: configService.get('BLIMU_API_SECRET_KEY'),\n * getUserId: (req) => req.user.id, // req is typed as AuthenticatedRequest\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * Using with custom provider:\n * ```typescript\n * @Module({\n * imports: [\n * BlimuModule.forRootAsync({\n * imports: [MyConfigModule],\n * useFactory: async (myConfigService: MyConfigService) => {\n * const config = await myConfigService.getBlimuConfig();\n * return {\n * apiKey: config.apiKey,\n * baseURL: config.baseUrl,\n * environmentId: config.environmentId,\n * getUserId: (req) => req.user?.id,\n * };\n * },\n * inject: [MyConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync<TRequest = unknown>(options: {\n global?: boolean | undefined;\n useFactory: (...args: unknown[]) => Promise<BlimuConfig<TRequest>> | BlimuConfig<TRequest>;\n inject?: (InjectionToken | OptionalFactoryDependency)[];\n imports?: (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference<() => Type<unknown>>\n )[];\n }): DynamicModule {\n const additionalImports = options.imports ?? [];\n\n const module = {\n ...(options.global ? { global: true } : {}),\n module: BlimuModule,\n imports: [...additionalImports] as (\n | Type<unknown>\n | DynamicModule\n | Promise<DynamicModule>\n | ForwardReference\n )[],\n providers: [\n // Register factory providers first so dependencies are available\n {\n provide: BLIMU_CONFIG,\n useFactory: async (...args: unknown[]) => {\n const configResult = options.useFactory(...args);\n const config = configResult instanceof Promise ? await configResult : configResult;\n return {\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n environmentId: config.environmentId,\n timeoutMs: config.timeoutMs ?? 30000,\n getUserId: config.getUserId,\n defaultEntitlementCtxResolver: config.defaultEntitlementCtxResolver,\n };\n },\n ...(options.inject ? { inject: options.inject } : {}),\n },\n {\n provide: Blimu,\n useFactory: (config: BlimuConfig) => {\n return new Blimu({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? 30000,\n });\n },\n inject: [BLIMU_CONFIG],\n },\n // Register class providers after their dependencies are available\n EntitlementGuard,\n JWKService,\n ],\n exports: [EntitlementGuard, Blimu, BLIMU_CONFIG, JWKService],\n };\n return module;\n }\n}\n","import type { EntitlementType, ResourceType } from '@blimu/types';\n/**\n * Configuration interface for Blimu NestJS integration\n */\nexport interface BlimuConfig<TRequest = unknown> {\n global?: boolean | undefined;\n /**\n * The API secret key for authenticating with Blimu Runtime API\n */\n apiKey: string;\n\n /**\n * The base URL for the Blimu Runtime API\n * @default 'https://api.blimu.dev'\n */\n baseURL?: string | undefined;\n\n /**\n * Environment ID for the Blimu environment\n * This will be used in future versions for environment-specific configurations\n */\n environmentId?: string | undefined;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeoutMs?: number | undefined;\n\n /**\n * Function to extract user ID from the request\n *\n * This function is called by the EntitlementGuard to determine which user\n * to check entitlements for. It should return the user ID as a string.\n *\n * @param request - The incoming HTTP request\n * @returns The user ID as a string, or a Promise that resolves to the user ID\n *\n * @example\n * ```typescript\n * // Extract from JWT token in Authorization header\n * getUserId: (req) => {\n * const token = req.headers.authorization?.replace('Bearer ', '');\n * const decoded = jwt.verify(token, secret);\n * return decoded.sub;\n * }\n *\n * // Extract from request.user (common with Passport.js)\n * getUserId: (req) => req.user?.id\n *\n * // Extract from custom header\n * getUserId: (req) => req.headers['x-user-id']\n * ```\n */\n getUserId: (request: TRequest) => string | Promise<string>;\n\n /**\n * Optional default entitlement context resolver for entitlement checks\n *\n * This function is used as a fallback when a decorator-specific resolver is not provided.\n * It receives an object with the entitlement key and parsed resource type, along with the request,\n * allowing for context-aware resolution.\n *\n * @param context - Object containing the entitlement and resource type (parsed from entitlement by splitting on ':')\n * @param request - The incoming HTTP request\n * @returns Entitlement context with resourceId and optionally amount, or a Promise that resolves to it\n *\n * @example\n * ```typescript\n * // Extract resourceId from path parameter\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * })\n *\n * // Context-aware resolution based on resource type\n * defaultEntitlementCtxResolver: ({ resourceType }, req) => {\n * if (resourceType === 'organization') {\n * return { resourceId: req.params.orgId };\n * }\n * return { resourceId: req.params.resourceId };\n * }\n *\n * // Extract from request body or query with amount for consumption\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.body?.resourceId || req.query?.resourceId,\n * amount: req.body?.amount, // Amount to consume for usage-based entitlements\n * })\n * ```\n */\n defaultEntitlementCtxResolver?: (\n context: { entitlement: EntitlementType; resourceType: ResourceType },\n request: TRequest,\n ) =>\n | { resourceId: string; amount?: number }\n | Promise<{ resourceId: string; amount?: number }>\n | undefined;\n}\n\n/**\n * Injection token for Blimu configuration\n */\nexport const BLIMU_CONFIG = Symbol('BLIMU_CONFIG');\n","import {\n type CanActivate,\n type ExecutionContext,\n ForbiddenException,\n Injectable,\n SetMetadata,\n Inject,\n} from '@nestjs/common';\nimport 'reflect-metadata';\n\nimport type { EntitlementType } from '@blimu/types';\nimport { BlimuForbiddenException } from '../exceptions/blimu-forbidden.exception';\nimport { Blimu } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\nexport const ENTITLEMENT_KEY = 'entitlement';\nexport const ENTITLEMENT_METADATA_KEY = Symbol('entitlement');\n\n/**\n * Entitlement context returned by the entitlementCtxResolver callback\n */\nexport interface EntitlementCtx {\n resourceId: string;\n amount?: number; // Amount to check against usage limit (for consumption)\n}\n\n/**\n * Metadata interface for entitlement checks\n */\nexport interface EntitlementMetadata<TRequest = unknown> {\n entitlementKey: EntitlementType;\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>;\n}\n\n/**\n * Sets entitlement metadata for a route handler\n * @internal This is used internally by the @Entitlement decorator\n */\nexport const SetEntitlementMetadata = <TRequest = unknown>(\n entitlementKey: string,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator =>\n SetMetadata(ENTITLEMENT_METADATA_KEY, {\n entitlementKey,\n entitlementCtxResolver,\n } as EntitlementMetadata<TRequest>);\n\n/**\n * Guard that checks if the authenticated user has the required entitlement on a resource\n *\n * This guard automatically:\n * 1. Extracts the user from the request\n * 2. Extracts the resource ID using the provided extractor function\n * 3. Calls the Blimu Runtime API to check entitlements\n * 4. Allows or denies access based on the result\n */\n@Injectable()\nexport class EntitlementGuard<TRequest = unknown> implements CanActivate {\n constructor(\n @Inject(BLIMU_CONFIG)\n private readonly config: BlimuConfig<TRequest>,\n @Inject(Blimu)\n private readonly runtime: Blimu,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<TRequest>();\n const handler = context.getHandler();\n const metadata = Reflect.getMetadata(ENTITLEMENT_METADATA_KEY, handler) as\n | EntitlementMetadata<TRequest>\n | undefined;\n\n if (!metadata) {\n // No entitlement check required\n return true;\n }\n\n // Extract user ID using the configured getUserId function\n let userId: string;\n try {\n userId = await this.config.getUserId(request);\n } catch {\n throw new ForbiddenException('Failed to extract user ID from request');\n }\n\n if (!userId) {\n throw new ForbiddenException('User ID is required for entitlement check');\n }\n\n // Resolve entitlement context from request\n // Priority: decorator resolver > default resolver > error\n let entitlementCtx: EntitlementCtx | undefined;\n if (metadata.entitlementCtxResolver) {\n entitlementCtx = await metadata.entitlementCtxResolver(request);\n } else if (this.config.defaultEntitlementCtxResolver) {\n // Parse resourceType from entitlementKey (format: \"resourceType:action\")\n const resourceType = metadata.entitlementKey.split(':')[0] || '';\n entitlementCtx = await this.config.defaultEntitlementCtxResolver(\n {\n entitlement: metadata.entitlementKey,\n resourceType,\n },\n request,\n );\n } else {\n throw new ForbiddenException('No entitlement context resolver available');\n }\n\n if (!entitlementCtx?.resourceId) {\n throw new ForbiddenException('Resource ID is required for entitlement check');\n }\n\n try {\n // Check entitlement\n const result = await this.runtime.entitlements.checkEntitlement({\n userId,\n entitlement: metadata.entitlementKey,\n resourceId: entitlementCtx.resourceId,\n ...(entitlementCtx.amount !== undefined ? { amount: entitlementCtx.amount } : {}),\n });\n\n if (!result.allowed) {\n throw new BlimuForbiddenException(\n result,\n metadata.entitlementKey,\n entitlementCtx.resourceId,\n userId,\n );\n }\n\n return true;\n } catch (error) {\n if (error instanceof BlimuForbiddenException || error instanceof ForbiddenException) {\n throw error;\n }\n\n // Log the error for debugging but don't expose internal details\n console.error('Entitlement check failed:', error);\n throw new ForbiddenException('Failed to verify entitlements');\n }\n }\n}\n","import { ForbiddenException } from '@nestjs/common';\nimport type { Schema } from '@blimu/backend';\nimport type { EntitlementType } from '@blimu/types';\n/**\n * Custom exception for Blimu entitlement check failures\n *\n * This exception extends NestJS's ForbiddenException and includes\n * the typed EntitlementCheckResult, providing detailed information\n * about why the entitlement check failed (roles, plans, limits, etc.)\n */\nexport class BlimuForbiddenException extends ForbiddenException {\n /**\n * The entitlement check result containing detailed failure information\n */\n public readonly entitlementResult: Schema.EntitlementCheckResult;\n\n /**\n * The entitlement key that was checked\n */\n public readonly entitlementKey: EntitlementType;\n\n /**\n * The resource ID that was checked\n */\n public readonly resourceId: string;\n\n /**\n * The user ID that was checked\n */\n public readonly userId: string;\n\n constructor(\n entitlementResult: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n resourceId: string,\n userId: string,\n ) {\n // Create a user-friendly message based on the failure reason\n const message = BlimuForbiddenException.buildMessage(entitlementResult, entitlementKey);\n\n super({\n message,\n entitlementResult,\n entitlementKey,\n resourceId,\n userId,\n });\n\n this.entitlementResult = entitlementResult;\n this.entitlementKey = entitlementKey;\n this.resourceId = resourceId;\n this.userId = userId;\n }\n\n /**\n * Builds a user-friendly error message from the entitlement check result\n */\n private static buildMessage(\n result: Schema.EntitlementCheckResult,\n entitlementKey: EntitlementType,\n ): string {\n const reasons: string[] = [];\n\n if (result.roles && !result.roles.allowed) {\n reasons.push(\n `Insufficient roles. Required: ${result.roles.allowedRoles?.join(', ') || 'unknown'}. User has: ${result.roles.userRoles?.join(', ') || 'none'}.`,\n );\n }\n\n if (result.plans && !result.plans.allowed) {\n reasons.push(\n `Plan restriction. Required plans: ${result.plans.allowedPlans?.join(', ') || 'unknown'}. Current plan: ${result.plans.plan || 'none'}.`,\n );\n }\n\n if (result.limit && !result.limit.allowed) {\n reasons.push(`Usage limit exceeded. ${result.limit.reason || 'Limit has been reached'}.`);\n }\n\n if (reasons.length === 0) {\n return `Access denied for entitlement: ${entitlementKey}`;\n }\n\n return `Access denied for entitlement \"${entitlementKey}\": ${reasons.join(' ')}`;\n }\n}\n","import { Inject, Injectable, Logger } from '@nestjs/common';\nimport { TokenVerifier } from '@blimu/backend';\nimport { BLIMU_CONFIG, type BlimuConfig } from '../config/blimu.config';\n\n@Injectable()\nexport class JWKService {\n private readonly logger = new Logger(JWKService.name);\n private readonly tokenVerifier: TokenVerifier;\n\n constructor(@Inject(BLIMU_CONFIG) private readonly config: BlimuConfig) {\n this.tokenVerifier = new TokenVerifier({\n runtimeApiUrl: this.config.baseURL,\n });\n }\n\n /**\n * Verify JWT token issued by Blimu's main auth (environment/session tokens).\n * Uses the configured API key and environment JWKS.\n */\n async verifyToken<T = unknown>(token: string): Promise<T> {\n try {\n this.logger.debug(\n `🔍 Verifying token. Runtime API URL: ${this.config.baseURL}, API Key prefix: ${this.config.apiKey?.substring(0, 10)}...`,\n );\n\n const result = await this.tokenVerifier.verifyToken<T>({\n secretKey: this.config.apiKey,\n token,\n runtimeApiUrl: this.config.baseURL,\n });\n\n this.logger.debug(`✅ Token verified successfully`);\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(`❌ Token verification failed: ${errorMessage}`);\n if (error instanceof Error && error.stack) {\n this.logger.error(`Stack trace: ${error.stack}`);\n }\n throw error;\n }\n }\n\n /**\n * Verify JWT access token issued by an OAuth2 app (e.g. device code or authorization code flow).\n * Uses the public OAuth JWKS endpoint; no API key required. Pass the OAuth app's client_id.\n */\n async verifyOAuthToken<T = unknown>(token: string, clientId: string): Promise<T> {\n try {\n this.logger.debug(\n `🔍 Verifying OAuth token. Runtime API URL: ${this.config.baseURL}, clientId: ${clientId}`,\n );\n\n const result = await this.tokenVerifier.verifyToken<T>({\n clientId,\n token,\n runtimeApiUrl: this.config.baseURL,\n });\n\n this.logger.debug(`✅ OAuth token verified successfully`);\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(`❌ OAuth token verification failed: ${errorMessage}`);\n if (error instanceof Error && error.stack) {\n this.logger.error(`Stack trace: ${error.stack}`);\n }\n throw error;\n }\n }\n\n /**\n * Clear cache (useful for testing or key rotation)\n */\n clearCache(): void {\n this.tokenVerifier.clearCache(this.config.apiKey);\n this.logger.debug('JWK cache cleared');\n }\n}\n","import { applyDecorators, UseGuards } from '@nestjs/common';\nimport {\n EntitlementGuard,\n SetEntitlementMetadata,\n type EntitlementCtx,\n} from '../guards/entitlement.guard';\nimport type { EntitlementType } from '@blimu/types';\n\n/**\n * Decorator to check if the authenticated user has a specific entitlement on a resource.\n *\n * This decorator combines the entitlement metadata setting and guard application\n * to provide a clean, declarative way to protect routes with entitlement checks.\n *\n * @param entitlementKey - The entitlement key to check (e.g., 'brand:read', 'organization:create_workspace')\n * @param entitlementCtxResolver - Optional function that returns entitlement context including resourceId and optionally amount for usage limits.\n * If not provided, the default resolver from module configuration will be used.\n *\n * @example\n * Basic usage with path parameter:\n * ```typescript\n * @Get('/:resourceId')\n * @Entitlement('brand:read', (req) => ({ resourceId: req.params.resourceId }))\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n *\n * @example\n * Using default resolver from module configuration:\n * ```typescript\n * // In module configuration:\n * BlimuModule.forRoot({\n * // ... other config\n * defaultEntitlementCtxResolver: ({ entitlement, resourceType }, req) => ({\n * resourceId: req.params.resourceId,\n * }),\n * })\n *\n * // In controller (no resolver needed):\n * @Get('/:resourceId')\n * @Entitlement('brand:read')\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // Uses default resolver from config\n * }\n * ```\n *\n * @example\n * Using with typed parameters:\n * ```typescript\n * @Get('/:resourceType/:resourceId')\n * @Entitlement('workspace:delete', (req) => ({ resourceId: req.params.resourceId }))\n * async deleteResource(@Param() params: ResourceParamsDto) {\n * // User is guaranteed to have 'workspace:delete' entitlement\n * }\n * ```\n *\n * @example\n * Complex resource ID extraction:\n * ```typescript\n * @Post('/organizations/:orgId/workspaces')\n * @Entitlement('organization:create_workspace', (req) => {\n * const params = req.params as { orgId: string };\n * return { resourceId: params.orgId };\n * })\n * async createWorkspace(@Param() params: CreateWorkspaceParamsDto, @Body() body: CreateWorkspaceDto) {\n * // User is guaranteed to have 'organization:create_workspace' entitlement on the organization\n * }\n * ```\n *\n * @example\n * With usage limit consumption:\n * ```typescript\n * @Post('/api-calls')\n * @Entitlement('organization:make_api_call', (req) => ({\n * resourceId: req.params.orgId,\n * amount: req.body.apiCallsCount, // Amount to consume from usage limit\n * }))\n * async makeApiCalls(@Param('orgId') orgId: string, @Body() body: { apiCallsCount: number }) {\n * // User is guaranteed to have 'organization:make_api_call' entitlement\n * // and sufficient usage limit balance\n * }\n * ```\n *\n * @example\n * Async resource context extraction (e.g., from database):\n * ```typescript\n * @Delete('/items/:itemId')\n * @Entitlement('workspace:delete_item', async (req) => {\n * // You could fetch the workspace ID from your database\n * const item = await itemService.findById(req.params.itemId);\n * return { resourceId: item.workspaceId };\n * })\n * async deleteItem(@Param('itemId') itemId: string) {\n * // User is guaranteed to have 'workspace:delete_item' entitlement on the item's workspace\n * }\n * ```\n *\n * @example\n * Using with custom request type:\n * ```typescript\n * interface AuthenticatedRequest {\n * user: { id: string; email: string };\n * }\n *\n * @Get('/:resourceId')\n * @Entitlement<AuthenticatedRequest>('brand:read', (req) => {\n * // req is typed as AuthenticatedRequest, so req.user is properly typed\n * console.log(req.user.email); // TypeScript knows this exists\n * return { resourceId: req.params.resourceId };\n * })\n * async getBrand(@Param('resourceId') resourceId: string) {\n * // User is guaranteed to have 'brand:read' entitlement on this resource\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const Entitlement = <TRequest = any>(\n entitlementKey: EntitlementType,\n entitlementCtxResolver?: (request: TRequest) => EntitlementCtx | Promise<EntitlementCtx>,\n): MethodDecorator => {\n return applyDecorators(\n SetEntitlementMetadata<TRequest>(entitlementKey, entitlementCtxResolver),\n UseGuards(EntitlementGuard),\n );\n};\n"],"mappings":"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","names":["ForbiddenException","ForbiddenException","Inject","Injectable","Injectable","Inject","Blimu","Blimu","config"]}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "https://github.com/blimu-dev/blimu-ts"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.2.
|
|
7
|
+
"version": "1.2.2",
|
|
8
8
|
"description": "NestJS integration library for Blimu authorization and entitlement system",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.cjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"rxjs": ">=7.0.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@blimu/backend": "1.2.
|
|
40
|
+
"@blimu/backend": "1.2.2",
|
|
41
41
|
"@blimu/types": "1.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|