@vritti/api-sdk 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +504 -43
- package/dist/index.cjs +1242 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +755 -2
- package/dist/index.d.ts +755 -2
- package/dist/index.js +1222 -5
- package/dist/index.js.map +1 -1
- package/package.json +23 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,756 @@
|
|
|
1
|
-
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
import { DynamicModule, OnModuleDestroy, OnModuleInit, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
3
|
+
import { ConfigService } from '@nestjs/config';
|
|
4
|
+
import { Reflector } from '@nestjs/core';
|
|
5
|
+
import { JwtService } from '@nestjs/jwt';
|
|
6
|
+
import { FastifyRequest } from 'fastify';
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Global authentication configuration module
|
|
10
|
+
*
|
|
11
|
+
* This module provides:
|
|
12
|
+
* - JWT token verification (JwtModule)
|
|
13
|
+
* - Global authentication guard (VrittiAuthGuard)
|
|
14
|
+
* - Support for @Public and @Onboarding decorators
|
|
15
|
+
*
|
|
16
|
+
* ## Features:
|
|
17
|
+
* - Automatically applies VrittiAuthGuard to all routes
|
|
18
|
+
* - Configures JwtModule with JWT_SECRET from environment
|
|
19
|
+
* - Exports JwtModule for token generation in services
|
|
20
|
+
*
|
|
21
|
+
* ## Usage in Application:
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // In app.module.ts
|
|
25
|
+
* @Module({
|
|
26
|
+
* imports: [
|
|
27
|
+
* ConfigModule.forRoot({ isGlobal: true }),
|
|
28
|
+
*
|
|
29
|
+
* // Auth configuration (global guard + JWT)
|
|
30
|
+
* AuthConfigModule.forRootAsync(),
|
|
31
|
+
*
|
|
32
|
+
* // Database configuration (Gateway mode)
|
|
33
|
+
* DatabaseModule.forServer({
|
|
34
|
+
* useFactory: (config: ConfigService) => ({
|
|
35
|
+
* primaryDb: {
|
|
36
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
37
|
+
* // ... other config
|
|
38
|
+
* },
|
|
39
|
+
* prismaClientConstructor: PrismaClient,
|
|
40
|
+
* }),
|
|
41
|
+
* inject: [ConfigService],
|
|
42
|
+
* }),
|
|
43
|
+
* ],
|
|
44
|
+
* })
|
|
45
|
+
* export class AppModule {}
|
|
46
|
+
*
|
|
47
|
+
* ## Environment Variables Required:
|
|
48
|
+
* - JWT_SECRET: Secret key to verify access tokens (required)
|
|
49
|
+
* - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)
|
|
50
|
+
*
|
|
51
|
+
* ## Bypass Authentication:
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // Skip authentication on specific endpoints
|
|
55
|
+
* @Public()
|
|
56
|
+
* @Post('auth/login')
|
|
57
|
+
* async login() { ... }
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // Onboarding endpoints (only accept onboarding tokens)
|
|
61
|
+
* @Onboarding()
|
|
62
|
+
* @Post('onboarding/verify-email')
|
|
63
|
+
* async verifyEmail(@Request() req) {
|
|
64
|
+
* const userId = req.user.id; // Available from guard
|
|
65
|
+
* ...
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
declare class AuthConfigModule {
|
|
69
|
+
/**
|
|
70
|
+
* Register the auth module with async configuration
|
|
71
|
+
*
|
|
72
|
+
* This method:
|
|
73
|
+
* 1. Configures JwtModule with JWT_SECRET from ConfigService
|
|
74
|
+
* 2. Provides VrittiAuthGuard globally (applies to all routes)
|
|
75
|
+
* 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
|
|
76
|
+
*
|
|
77
|
+
* @returns Dynamic module configuration
|
|
78
|
+
*/
|
|
79
|
+
static forRootAsync(): DynamicModule;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Primary database connection configuration
|
|
84
|
+
*/
|
|
85
|
+
interface PrimaryDbConfig {
|
|
86
|
+
/** Database host */
|
|
87
|
+
host: string;
|
|
88
|
+
/** Database port (default: 5432) */
|
|
89
|
+
port?: number;
|
|
90
|
+
/** Database username */
|
|
91
|
+
username: string;
|
|
92
|
+
/** Database password */
|
|
93
|
+
password: string;
|
|
94
|
+
/** Database name */
|
|
95
|
+
database: string;
|
|
96
|
+
/** Default schema (default: 'public') */
|
|
97
|
+
schema?: string;
|
|
98
|
+
/** SSL mode: 'require' | 'prefer' | 'disable' (default: 'require') */
|
|
99
|
+
sslMode?: 'require' | 'prefer' | 'disable';
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Configuration options for DatabaseModule
|
|
103
|
+
*/
|
|
104
|
+
interface DatabaseModuleOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Primary database configuration (for tenant registry queries)
|
|
107
|
+
* Only required in gateway mode
|
|
108
|
+
* @example
|
|
109
|
+
* primaryDb: {
|
|
110
|
+
* host: 'aws-pooler.supabase.com',
|
|
111
|
+
* port: 5432,
|
|
112
|
+
* username: 'postgres.xxx',
|
|
113
|
+
* password: 'xxx',
|
|
114
|
+
* database: 'postgres',
|
|
115
|
+
* schema: 'public',
|
|
116
|
+
* sslMode: 'require',
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
primaryDb: PrimaryDbConfig;
|
|
120
|
+
/**
|
|
121
|
+
* Primary database client constructor (for querying tenant registry)
|
|
122
|
+
* Only required in gateway mode
|
|
123
|
+
* @example import { PrismaClient } from '@prisma/client'
|
|
124
|
+
*/
|
|
125
|
+
prismaClientConstructor: any;
|
|
126
|
+
/**
|
|
127
|
+
* Connection cache TTL in milliseconds
|
|
128
|
+
* Idle connections will be closed after this period
|
|
129
|
+
* @default 300000 (5 minutes)
|
|
130
|
+
*/
|
|
131
|
+
connectionCacheTTL?: number;
|
|
132
|
+
/**
|
|
133
|
+
* Maximum number of concurrent connections per tenant
|
|
134
|
+
* @default 10
|
|
135
|
+
*/
|
|
136
|
+
maxConnections?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Encryption key for decrypting database credentials
|
|
139
|
+
* Required if tenant config stores encrypted passwords
|
|
140
|
+
*/
|
|
141
|
+
encryptionKey?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Tenant configuration stored in cloud database
|
|
146
|
+
* This is the shape of data returned from the tenant registry
|
|
147
|
+
*
|
|
148
|
+
* Note: Database configuration is now stored in a separate TenantDatabaseConfig table
|
|
149
|
+
* but is flattened into this interface for convenience.
|
|
150
|
+
*/
|
|
151
|
+
interface TenantInfo {
|
|
152
|
+
/** Unique tenant identifier */
|
|
153
|
+
id: string;
|
|
154
|
+
/** Human-readable tenant slug */
|
|
155
|
+
subdomain: string;
|
|
156
|
+
/** Tenant type - SHARED or DEDICATED */
|
|
157
|
+
type: 'SHARED' | 'DEDICATED';
|
|
158
|
+
/** Tenant status */
|
|
159
|
+
status: string;
|
|
160
|
+
/** For SHARED tenants: schema name within the shared database */
|
|
161
|
+
schemaName?: string;
|
|
162
|
+
/** For DEDICATED tenants: database configuration (from TenantDatabaseConfig table) */
|
|
163
|
+
databaseName?: string;
|
|
164
|
+
databaseHost?: string;
|
|
165
|
+
databasePort?: number;
|
|
166
|
+
databaseUsername?: string;
|
|
167
|
+
databasePassword?: string;
|
|
168
|
+
databaseSslMode?: string;
|
|
169
|
+
connectionPoolSize?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Dynamic module for multi-tenant database management
|
|
174
|
+
*
|
|
175
|
+
* This module provides:
|
|
176
|
+
* - Tenant context management (request-scoped)
|
|
177
|
+
* - Database connection pooling
|
|
178
|
+
* - Dynamic schema/cluster routing
|
|
179
|
+
* - Support for both gateway and microservice modes
|
|
180
|
+
*
|
|
181
|
+
* ## Gateway Mode (API Gateway)
|
|
182
|
+
* - Use DatabaseModule.forServer() method
|
|
183
|
+
* - Automatically extracts tenant from subdomain, falls back to x-tenant-id header
|
|
184
|
+
* - Provide primaryDb configuration and prismaClientConstructor
|
|
185
|
+
* - Automatically queries primary DB for tenant config
|
|
186
|
+
* - Automatically registers TenantContextInterceptor globally
|
|
187
|
+
* - No manual interceptor registration needed
|
|
188
|
+
*
|
|
189
|
+
* ## Microservice Mode (RabbitMQ Workers)
|
|
190
|
+
* - Use DatabaseModule.forMicroservice() method
|
|
191
|
+
* - Only provide prismaClientConstructor
|
|
192
|
+
* - Tenant context comes from RabbitMQ messages
|
|
193
|
+
* - Automatically registers MessageTenantContextInterceptor globally
|
|
194
|
+
* - No manual interceptor registration needed
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* // Gateway configuration
|
|
198
|
+
* DatabaseModule.forServer({
|
|
199
|
+
* inject: [ConfigService],
|
|
200
|
+
* useFactory: (config: ConfigService) => ({
|
|
201
|
+
* primaryDb: {
|
|
202
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
203
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
204
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
205
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
206
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
207
|
+
* },
|
|
208
|
+
* prismaClientConstructor: PrismaClient,
|
|
209
|
+
* }),
|
|
210
|
+
* })
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* // Microservice configuration
|
|
214
|
+
* DatabaseModule.forMicroservice({
|
|
215
|
+
* inject: [ConfigService],
|
|
216
|
+
* useFactory: (config: ConfigService) => ({
|
|
217
|
+
* prismaClientConstructor: PrismaClient,
|
|
218
|
+
* }),
|
|
219
|
+
* })
|
|
220
|
+
*/
|
|
221
|
+
declare class DatabaseModule {
|
|
222
|
+
/**
|
|
223
|
+
* Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
|
|
224
|
+
*
|
|
225
|
+
* This mode is for API Gateways that handle HTTP requests:
|
|
226
|
+
* - Automatically registers TenantContextInterceptor
|
|
227
|
+
* - Extracts tenant from subdomain or x-tenant-id header
|
|
228
|
+
* - Queries primary database for tenant configuration
|
|
229
|
+
* - Provides PrimaryDatabaseService for tenant lookup
|
|
230
|
+
*
|
|
231
|
+
* @param options Async configuration options
|
|
232
|
+
* @returns Dynamic module configuration with HTTP interceptor
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* DatabaseModule.forServer({
|
|
236
|
+
* inject: [ConfigService],
|
|
237
|
+
* useFactory: (config: ConfigService) => ({
|
|
238
|
+
* primaryDb: {
|
|
239
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
240
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
241
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
242
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
243
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
244
|
+
* },
|
|
245
|
+
* prismaClientConstructor: PrismaClient,
|
|
246
|
+
* }),
|
|
247
|
+
* })
|
|
248
|
+
*/
|
|
249
|
+
static forServer(options: {
|
|
250
|
+
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
|
|
251
|
+
inject?: any[];
|
|
252
|
+
}): DynamicModule;
|
|
253
|
+
/**
|
|
254
|
+
* Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
|
|
255
|
+
*
|
|
256
|
+
* This mode is for microservices that process messages from queues:
|
|
257
|
+
* - Automatically registers MessageTenantContextInterceptor
|
|
258
|
+
* - Extracts tenant from RabbitMQ message patterns
|
|
259
|
+
* - No primary database needed (tenant comes from message context)
|
|
260
|
+
*
|
|
261
|
+
* @param options Async configuration options
|
|
262
|
+
* @returns Dynamic module configuration with message interceptor
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* DatabaseModule.forMicroservice({
|
|
266
|
+
* inject: [ConfigService],
|
|
267
|
+
* useFactory: (config: ConfigService) => ({
|
|
268
|
+
* prismaClientConstructor: PrismaClient,
|
|
269
|
+
* }),
|
|
270
|
+
* })
|
|
271
|
+
*/
|
|
272
|
+
static forMicroservice(options: {
|
|
273
|
+
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
|
|
274
|
+
inject?: any[];
|
|
275
|
+
}): DynamicModule;
|
|
276
|
+
/**
|
|
277
|
+
* Internal helper to create dynamic module with conditional interceptor registration
|
|
278
|
+
*
|
|
279
|
+
* @param options Configuration options
|
|
280
|
+
* @param mode Mode of operation (gateway or microservice)
|
|
281
|
+
* @returns Dynamic module configuration
|
|
282
|
+
*/
|
|
283
|
+
private static createDynamicModule;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Request-scoped service that holds tenant context for the current request or RabbitMQ message
|
|
288
|
+
*
|
|
289
|
+
* IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance
|
|
290
|
+
* for each HTTP request or RabbitMQ message. This ensures tenant isolation and
|
|
291
|
+
* prevents cross-tenant data leaks in concurrent scenarios.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* // In a controller or service
|
|
295
|
+
* constructor(private readonly tenantContext: TenantContextService) {}
|
|
296
|
+
*
|
|
297
|
+
* async handleRequest() {
|
|
298
|
+
* const tenant = this.tenantContext.getTenant();
|
|
299
|
+
* console.log(`Processing request for tenant: ${tenant.tenantSlug}`);
|
|
300
|
+
* }
|
|
301
|
+
*/
|
|
302
|
+
declare class TenantContextService {
|
|
303
|
+
private tenantInfo;
|
|
304
|
+
/**
|
|
305
|
+
* Set tenant information for this request/message
|
|
306
|
+
*
|
|
307
|
+
* This is typically called by:
|
|
308
|
+
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
309
|
+
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
310
|
+
* - Manual context setup in message handlers
|
|
311
|
+
*
|
|
312
|
+
* @param tenantInfo Complete tenant information
|
|
313
|
+
* @throws Error if tenant context is already set (prevents accidental overwrites)
|
|
314
|
+
*/
|
|
315
|
+
setTenant(tenantInfo: TenantInfo): void;
|
|
316
|
+
/**
|
|
317
|
+
* Get tenant information for this request/message
|
|
318
|
+
*
|
|
319
|
+
* @returns Tenant information
|
|
320
|
+
* @throws UnauthorizedException if tenant context hasn't been set
|
|
321
|
+
*/
|
|
322
|
+
getTenant(): TenantInfo;
|
|
323
|
+
/**
|
|
324
|
+
* Check if tenant context has been set
|
|
325
|
+
*
|
|
326
|
+
* @returns true if tenant context is available
|
|
327
|
+
*/
|
|
328
|
+
hasTenant(): boolean;
|
|
329
|
+
/**
|
|
330
|
+
* Clear tenant context
|
|
331
|
+
*
|
|
332
|
+
* This is useful for cleanup in RabbitMQ message handlers
|
|
333
|
+
* after the message has been processed.
|
|
334
|
+
*
|
|
335
|
+
* HTTP requests don't need manual cleanup as the service
|
|
336
|
+
* instance is destroyed when the request ends.
|
|
337
|
+
*/
|
|
338
|
+
clearTenant(): void;
|
|
339
|
+
/**
|
|
340
|
+
* Get tenant ID safely (returns null if not set)
|
|
341
|
+
*
|
|
342
|
+
* @returns Tenant ID or null
|
|
343
|
+
*/
|
|
344
|
+
getTenantIdSafe(): string | null;
|
|
345
|
+
/**
|
|
346
|
+
* Get tenant subdomain safely (returns null if not set)
|
|
347
|
+
*
|
|
348
|
+
* @returns Tenant subdomain or null
|
|
349
|
+
*/
|
|
350
|
+
getTenantSubdomainSafe(): string | null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Service responsible for managing tenant-scoped database connections
|
|
355
|
+
*
|
|
356
|
+
* This service:
|
|
357
|
+
* - Maintains a connection pool (Map<cacheKey, DbClient>)
|
|
358
|
+
* - Creates new connections dynamically based on tenant context
|
|
359
|
+
* - Reuses existing connections for the same tenant
|
|
360
|
+
* - Supports both cloud schemas and enterprise databases
|
|
361
|
+
* - Automatically cleans up idle connections
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* // In a controller or service
|
|
365
|
+
* const dbClient = await this.tenantDatabase.getDbClient<PrismaClient>();
|
|
366
|
+
* const users = await dbClient.user.findMany();
|
|
367
|
+
*/
|
|
368
|
+
declare class TenantDatabaseService implements OnModuleDestroy {
|
|
369
|
+
private readonly options;
|
|
370
|
+
private readonly tenantContext;
|
|
371
|
+
private readonly logger;
|
|
372
|
+
/** Connection pool: Map<cacheKey, DbClient> */
|
|
373
|
+
private readonly clients;
|
|
374
|
+
/** Track last usage time for idle connection cleanup */
|
|
375
|
+
private readonly clientLastUsed;
|
|
376
|
+
/** Cleanup interval timer */
|
|
377
|
+
private cleanupInterval?;
|
|
378
|
+
constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
|
|
379
|
+
/**
|
|
380
|
+
* Get tenant-scoped database client for the current request/message
|
|
381
|
+
*
|
|
382
|
+
* This method:
|
|
383
|
+
* 1. Gets tenant info from TenantContextService
|
|
384
|
+
* 2. Builds a connection URL based on tenant type
|
|
385
|
+
* 3. Returns cached client if exists, otherwise creates new one
|
|
386
|
+
*
|
|
387
|
+
* @returns Promise<Database client instance>
|
|
388
|
+
* @throws UnauthorizedException if tenant context not set
|
|
389
|
+
* @throws InternalServerErrorException if connection fails
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
393
|
+
* const users = await dbClient.user.findMany();
|
|
394
|
+
*/
|
|
395
|
+
getDbClient<T = any>(): Promise<T>;
|
|
396
|
+
/**
|
|
397
|
+
* Create a new database client for the given tenant
|
|
398
|
+
*/
|
|
399
|
+
private createDbClient;
|
|
400
|
+
/**
|
|
401
|
+
* Build connection URL for enterprise tenant (dedicated database)
|
|
402
|
+
*/
|
|
403
|
+
private buildTenantDbUrl;
|
|
404
|
+
/**
|
|
405
|
+
* Build cache key for connection pooling
|
|
406
|
+
*/
|
|
407
|
+
private buildCacheKey;
|
|
408
|
+
/**
|
|
409
|
+
* Start periodic cleanup of idle connections
|
|
410
|
+
*/
|
|
411
|
+
private startConnectionCleaner;
|
|
412
|
+
/**
|
|
413
|
+
* Clean up idle connections that haven't been used recently
|
|
414
|
+
*/
|
|
415
|
+
private cleanupIdleConnections;
|
|
416
|
+
/**
|
|
417
|
+
* Get current connection pool statistics
|
|
418
|
+
*/
|
|
419
|
+
getPoolStats(): {
|
|
420
|
+
activeConnections: number;
|
|
421
|
+
tenants: string[];
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* Mask password in connection URL for logging
|
|
425
|
+
*/
|
|
426
|
+
private maskPassword;
|
|
427
|
+
onModuleDestroy(): Promise<void>;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Service responsible for querying the primary database to resolve tenant configurations
|
|
432
|
+
*
|
|
433
|
+
* This service:
|
|
434
|
+
* - Connects to the primary database (tenant registry)
|
|
435
|
+
* - Queries tenant metadata (database location, credentials, etc.)
|
|
436
|
+
* - Caches tenant configs in memory to reduce database load
|
|
437
|
+
* - Only used in GATEWAY MODE (microservices receive tenant config from messages)
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
* // In API Gateway
|
|
441
|
+
* const config = await primaryDatabase.getTenantConfig('acme');
|
|
442
|
+
* // Returns: { id, slug, type, databaseHost, databaseName, ... }
|
|
443
|
+
*/
|
|
444
|
+
declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
445
|
+
private readonly options;
|
|
446
|
+
private readonly logger;
|
|
447
|
+
/** Primary database client for querying tenant registry */
|
|
448
|
+
private primaryDbClient;
|
|
449
|
+
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
450
|
+
private readonly tenantConfigCache;
|
|
451
|
+
/** Cache TTL in milliseconds */
|
|
452
|
+
private readonly cacheTTL;
|
|
453
|
+
constructor(options: DatabaseModuleOptions);
|
|
454
|
+
onModuleInit(): Promise<void>;
|
|
455
|
+
/**
|
|
456
|
+
* Initialize connection to primary database
|
|
457
|
+
*/
|
|
458
|
+
private initializePrimaryDbClient;
|
|
459
|
+
/**
|
|
460
|
+
* Build connection URL from primary database properties
|
|
461
|
+
*/
|
|
462
|
+
private buildPrimaryDbUrl;
|
|
463
|
+
/**
|
|
464
|
+
* Mask password in connection URL for logging
|
|
465
|
+
*/
|
|
466
|
+
private maskPassword;
|
|
467
|
+
/**
|
|
468
|
+
* Get tenant configuration by identifier (ID or slug)
|
|
469
|
+
*
|
|
470
|
+
* @param tenantIdentifier Tenant ID or slug
|
|
471
|
+
* @returns Tenant configuration or null if not found
|
|
472
|
+
*/
|
|
473
|
+
getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
|
|
474
|
+
/**
|
|
475
|
+
* Cache tenant information with TTL
|
|
476
|
+
*/
|
|
477
|
+
private cacheInfo;
|
|
478
|
+
/**
|
|
479
|
+
* Clear cached tenant information
|
|
480
|
+
*
|
|
481
|
+
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
482
|
+
*
|
|
483
|
+
* @param tenantIdentifier Tenant ID or slug
|
|
484
|
+
*/
|
|
485
|
+
clearTenantCache(tenantIdentifier: string): void;
|
|
486
|
+
/**
|
|
487
|
+
* Clear all cached tenant configurations
|
|
488
|
+
*/
|
|
489
|
+
clearAllCaches(): void;
|
|
490
|
+
/**
|
|
491
|
+
* Get primary database client for direct database access
|
|
492
|
+
*
|
|
493
|
+
* This is useful for platform admin operations (creating tenants, billing, etc.)
|
|
494
|
+
*
|
|
495
|
+
* @returns Primary database client instance
|
|
496
|
+
* @throws Error if primary database client is not initialized
|
|
497
|
+
*/
|
|
498
|
+
getPrimaryDbClient<T = any>(): T;
|
|
499
|
+
/**
|
|
500
|
+
* Decrypt database credentials
|
|
501
|
+
*
|
|
502
|
+
* Override this method to implement your encryption strategy
|
|
503
|
+
*
|
|
504
|
+
* @param encrypted Encrypted value
|
|
505
|
+
* @returns Decrypted value
|
|
506
|
+
*/
|
|
507
|
+
private decrypt;
|
|
508
|
+
onModuleDestroy(): Promise<void>;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
declare class RequestService {
|
|
512
|
+
private readonly request;
|
|
513
|
+
constructor(request: FastifyRequest);
|
|
514
|
+
/**
|
|
515
|
+
* Extract tenant identifier from request headers
|
|
516
|
+
* Priority: x-tenant-id > x-subdomain
|
|
517
|
+
* @returns Tenant identifier or null if not found
|
|
518
|
+
*/
|
|
519
|
+
getTenantIdentifier(): string | null;
|
|
520
|
+
/**
|
|
521
|
+
* Extract access token from Authorization header
|
|
522
|
+
* Format: "Bearer <token>"
|
|
523
|
+
* @returns Access token or null if not found
|
|
524
|
+
*/
|
|
525
|
+
getAccessToken(): string | null;
|
|
526
|
+
/**
|
|
527
|
+
* Extract refresh token from session-id cookie
|
|
528
|
+
* Cookie name: session-id
|
|
529
|
+
* @returns Refresh token or null if not found
|
|
530
|
+
*/
|
|
531
|
+
getRefreshToken(): string | null;
|
|
532
|
+
/**
|
|
533
|
+
* Get a specific header value
|
|
534
|
+
* @param key Header key
|
|
535
|
+
* @returns Header value (string, array, or undefined)
|
|
536
|
+
*/
|
|
537
|
+
getHeader(key: string): string | string[] | undefined;
|
|
538
|
+
/**
|
|
539
|
+
* Get all headers
|
|
540
|
+
* @returns Record of all headers
|
|
541
|
+
*/
|
|
542
|
+
getAllHeaders(): FastifyRequest['headers'];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Vritti Authentication Guard - Validates JWT tokens and tenant context
|
|
547
|
+
*
|
|
548
|
+
* This guard performs comprehensive validation and attaches user data to request.
|
|
549
|
+
*
|
|
550
|
+
* Validation Flow:
|
|
551
|
+
* 1. Checks if endpoint is marked with @Public() decorator → skip all validation
|
|
552
|
+
* 2. Checks if endpoint is marked with @Onboarding() decorator:
|
|
553
|
+
* - Requires token type='onboarding'
|
|
554
|
+
* - Validates JWT signature and expiry only
|
|
555
|
+
* - Skips tenant and refresh token validation
|
|
556
|
+
* - Attaches user data to request.user
|
|
557
|
+
* 3. For regular endpoints (no decorator):
|
|
558
|
+
* - Rejects tokens with type='onboarding'
|
|
559
|
+
* - Validates access token (JWT signature, expiry, nbf)
|
|
560
|
+
* - Validates refresh token from session-id cookie
|
|
561
|
+
* - Validates tenant exists and is ACTIVE
|
|
562
|
+
* - Attaches user data to request.user
|
|
563
|
+
*
|
|
564
|
+
* Token Format:
|
|
565
|
+
* - Access Token: "Authorization: Bearer <jwt_token>"
|
|
566
|
+
* - Refresh Token: "session-id" cookie
|
|
567
|
+
*
|
|
568
|
+
* Token Types:
|
|
569
|
+
* - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)
|
|
570
|
+
* - type='access': Full access to authenticated endpoints
|
|
571
|
+
*
|
|
572
|
+
* Environment Variables Required:
|
|
573
|
+
* - JWT_SECRET: Secret key to verify access tokens (required)
|
|
574
|
+
* - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)
|
|
575
|
+
*
|
|
576
|
+
* Error Responses:
|
|
577
|
+
* - 401: Invalid/expired access token
|
|
578
|
+
* - 401: Invalid/expired refresh token
|
|
579
|
+
* - 401: Tenant not found or inactive
|
|
580
|
+
* - 401: Tenant identifier not found
|
|
581
|
+
* - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* // Automatically registered by AuthConfigModule.forRootAsync()
|
|
585
|
+
* // No manual registration needed
|
|
586
|
+
* //
|
|
587
|
+
* // Internal registration uses useExisting pattern:
|
|
588
|
+
* // providers: [
|
|
589
|
+
* // VrittiAuthGuard,
|
|
590
|
+
* // {
|
|
591
|
+
* // provide: APP_GUARD,
|
|
592
|
+
* // useExisting: VrittiAuthGuard,
|
|
593
|
+
* // },
|
|
594
|
+
* // ]
|
|
595
|
+
*
|
|
596
|
+
* @example
|
|
597
|
+
* // Bypass guard with @Public() decorator
|
|
598
|
+
* @Public()
|
|
599
|
+
* @Post('auth/login')
|
|
600
|
+
* async login(@Body() dto: LoginDto) { ... }
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* // Restrict to onboarding tokens with @Onboarding() decorator
|
|
604
|
+
* @Onboarding()
|
|
605
|
+
* @Post('onboarding/verify-email')
|
|
606
|
+
* async verifyEmail(@Request() req) {
|
|
607
|
+
* const userId = req.user.id; // Available from guard
|
|
608
|
+
* ...
|
|
609
|
+
* }
|
|
610
|
+
*/
|
|
611
|
+
declare class VrittiAuthGuard implements CanActivate {
|
|
612
|
+
private readonly reflector;
|
|
613
|
+
private readonly configService;
|
|
614
|
+
private readonly jwtService;
|
|
615
|
+
private readonly primaryDatabase;
|
|
616
|
+
private readonly requestService;
|
|
617
|
+
private readonly logger;
|
|
618
|
+
constructor(reflector: Reflector, configService: ConfigService, jwtService: JwtService, primaryDatabase: PrimaryDatabaseService, requestService: RequestService);
|
|
619
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
620
|
+
/**
|
|
621
|
+
* Validate access token with proper expiry checks
|
|
622
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
623
|
+
*/
|
|
624
|
+
private validateAccessToken;
|
|
625
|
+
/**
|
|
626
|
+
* Validate refresh token with proper expiry checks
|
|
627
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
628
|
+
*/
|
|
629
|
+
private validateRefreshToken;
|
|
630
|
+
/**
|
|
631
|
+
* Helper to validate refresh token with specific secret
|
|
632
|
+
*/
|
|
633
|
+
private validateRefreshTokenWithSecret;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Parameter decorator that injects tenant metadata into controller method
|
|
638
|
+
*
|
|
639
|
+
* This decorator retrieves tenant information (ID, slug, type, etc.)
|
|
640
|
+
* from the REQUEST-SCOPED TenantContextService.
|
|
641
|
+
*
|
|
642
|
+
* Useful for:
|
|
643
|
+
* - Logging tenant-specific information
|
|
644
|
+
* - Implementing tenant-specific business logic
|
|
645
|
+
* - Auditing and tracking
|
|
646
|
+
* - Conditional feature flags
|
|
647
|
+
*
|
|
648
|
+
* @returns TenantInfo object with tenant metadata
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* // Access tenant metadata
|
|
652
|
+
* @Get('info')
|
|
653
|
+
* async getTenantInfo(@Tenant() tenant: TenantInfo) {
|
|
654
|
+
* return {
|
|
655
|
+
* id: tenant.id,
|
|
656
|
+
* subdomain: tenant.subdomain,
|
|
657
|
+
* type: tenant.type,
|
|
658
|
+
* };
|
|
659
|
+
* }
|
|
660
|
+
*
|
|
661
|
+
* @example
|
|
662
|
+
* // Use for logging
|
|
663
|
+
* @Post()
|
|
664
|
+
* async createUser(
|
|
665
|
+
* @Body() dto: CreateUserDto,
|
|
666
|
+
* @Tenant() tenant: TenantInfo,
|
|
667
|
+
* ) {
|
|
668
|
+
* this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);
|
|
669
|
+
* // ...
|
|
670
|
+
* }
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* // Conditional business logic
|
|
674
|
+
* @Get('features')
|
|
675
|
+
* async getFeatures(@Tenant() tenant: TenantInfo) {
|
|
676
|
+
* if (tenant.type === 'ENTERPRISE') {
|
|
677
|
+
* return ['feature-a', 'feature-b', 'feature-c'];
|
|
678
|
+
* }
|
|
679
|
+
* return ['feature-a'];
|
|
680
|
+
* }
|
|
681
|
+
*/
|
|
682
|
+
declare const Tenant: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Onboarding Decorator - Marks endpoints that require onboarding token
|
|
686
|
+
*
|
|
687
|
+
* Use this decorator on controllers or route handlers that should only be
|
|
688
|
+
* accessible during the onboarding flow with JWT tokens containing type='onboarding'.
|
|
689
|
+
*
|
|
690
|
+
* These endpoints:
|
|
691
|
+
* - Accept ONLY tokens with type='onboarding'
|
|
692
|
+
* - Reject regular access tokens (type='access')
|
|
693
|
+
* - Skip tenant validation and refresh token checks
|
|
694
|
+
* - Only validate JWT signature and expiry
|
|
695
|
+
*
|
|
696
|
+
* Useful for:
|
|
697
|
+
* - Email/phone verification during onboarding
|
|
698
|
+
* - Onboarding status checks
|
|
699
|
+
* - Resending OTPs during registration
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* // On a controller method
|
|
703
|
+
* @Post('verify-email')
|
|
704
|
+
* @Onboarding()
|
|
705
|
+
* async verifyEmail(@Request() req, @Body() dto: VerifyEmailDto) {
|
|
706
|
+
* const userId = req.user.id; // Available from VrittiAuthGuard
|
|
707
|
+
* return this.service.verifyEmail(userId, dto.otp);
|
|
708
|
+
* }
|
|
709
|
+
*
|
|
710
|
+
* @example
|
|
711
|
+
* // Multiple onboarding endpoints
|
|
712
|
+
* @Controller('onboarding')
|
|
713
|
+
* export class OnboardingController {
|
|
714
|
+
* @Post('verify-email')
|
|
715
|
+
* @Onboarding()
|
|
716
|
+
* async verifyEmail() { ... }
|
|
717
|
+
*
|
|
718
|
+
* @Post('resend-otp')
|
|
719
|
+
* @Onboarding()
|
|
720
|
+
* async resendOtp() { ... }
|
|
721
|
+
* }
|
|
722
|
+
*/
|
|
723
|
+
declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Public Decorator - Marks endpoints that don't require authentication
|
|
727
|
+
*
|
|
728
|
+
* Use this decorator on controllers or route handlers to bypass VrittiAuthGuard
|
|
729
|
+
* tenant validation. Useful for:
|
|
730
|
+
* - Login/signup endpoints
|
|
731
|
+
* - Health checks
|
|
732
|
+
* - Public documentation endpoints
|
|
733
|
+
* - Webhook endpoints that don't require tenant context
|
|
734
|
+
*
|
|
735
|
+
* @example
|
|
736
|
+
* // On a controller method
|
|
737
|
+
* @Public()
|
|
738
|
+
* @Post('auth/login')
|
|
739
|
+
* async login(@Body() dto: LoginDto) {
|
|
740
|
+
* return this.authService.login(dto);
|
|
741
|
+
* }
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* // On an entire controller
|
|
745
|
+
* @Public()
|
|
746
|
+
* @Controller('health')
|
|
747
|
+
* export class HealthController {
|
|
748
|
+
* @Get()
|
|
749
|
+
* check() {
|
|
750
|
+
* return { status: 'ok' };
|
|
751
|
+
* }
|
|
752
|
+
* }
|
|
753
|
+
*/
|
|
754
|
+
declare const Public: () => _nestjs_common.CustomDecorator<string>;
|
|
755
|
+
|
|
756
|
+
export { AuthConfigModule, DatabaseModule, type DatabaseModuleOptions, Onboarding, PrimaryDatabaseService, type PrimaryDbConfig, Public, Tenant, TenantContextService, TenantDatabaseService, type TenantInfo, VrittiAuthGuard };
|