@vritti/api-sdk 0.0.2 → 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 +754 -336
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +394 -172
- package/dist/index.d.ts +394 -172
- package/dist/index.js +738 -331
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
import { Global, Module } from "@nestjs/common";
|
|
4
|
+
// src/auth/auth-config.module.ts
|
|
5
|
+
import { Global as Global2, Module as Module2 } from "@nestjs/common";
|
|
6
|
+
import { ConfigModule, ConfigService as ConfigService2 } from "@nestjs/config";
|
|
7
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
8
|
+
import { JwtModule } from "@nestjs/jwt";
|
|
6
9
|
|
|
7
|
-
// src/
|
|
8
|
-
|
|
10
|
+
// src/request/request.module.ts
|
|
11
|
+
import { Global, Module } from "@nestjs/common";
|
|
9
12
|
|
|
10
|
-
// src/
|
|
11
|
-
import { Inject, Injectable,
|
|
13
|
+
// src/request/services/request.service.ts
|
|
14
|
+
import { Inject, Injectable, Scope } from "@nestjs/common";
|
|
15
|
+
import { REQUEST } from "@nestjs/core";
|
|
12
16
|
function _ts_decorate(decorators, target, key, desc) {
|
|
13
17
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
18
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -26,6 +30,141 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
__name(_ts_param, "_ts_param");
|
|
33
|
+
var RequestService = class {
|
|
34
|
+
static {
|
|
35
|
+
__name(this, "RequestService");
|
|
36
|
+
}
|
|
37
|
+
request;
|
|
38
|
+
constructor(request) {
|
|
39
|
+
this.request = request;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract tenant identifier from request headers
|
|
43
|
+
* Priority: x-tenant-id > x-subdomain
|
|
44
|
+
* @returns Tenant identifier or null if not found
|
|
45
|
+
*/
|
|
46
|
+
getTenantIdentifier() {
|
|
47
|
+
const getHeader = /* @__PURE__ */ __name((key) => {
|
|
48
|
+
const value = this.request.headers?.[key];
|
|
49
|
+
return Array.isArray(value) ? value[0] : value;
|
|
50
|
+
}, "getHeader");
|
|
51
|
+
return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract access token from Authorization header
|
|
55
|
+
* Format: "Bearer <token>"
|
|
56
|
+
* @returns Access token or null if not found
|
|
57
|
+
*/
|
|
58
|
+
getAccessToken() {
|
|
59
|
+
const authHeader = this.request.headers?.authorization;
|
|
60
|
+
if (!authHeader) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const [type, token] = authHeader.split(" ") ?? [];
|
|
64
|
+
return type === "Bearer" && token ? token : null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract refresh token from session-id cookie
|
|
68
|
+
* Cookie name: session-id
|
|
69
|
+
* @returns Refresh token or null if not found
|
|
70
|
+
*/
|
|
71
|
+
getRefreshToken() {
|
|
72
|
+
try {
|
|
73
|
+
const cookies = this.request.cookies;
|
|
74
|
+
if (cookies && typeof cookies === "object") {
|
|
75
|
+
const sessionId = cookies["session-id"];
|
|
76
|
+
if (sessionId) {
|
|
77
|
+
return sessionId;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a specific header value
|
|
87
|
+
* @param key Header key
|
|
88
|
+
* @returns Header value (string, array, or undefined)
|
|
89
|
+
*/
|
|
90
|
+
getHeader(key) {
|
|
91
|
+
return this.request.headers?.[key];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get all headers
|
|
95
|
+
* @returns Record of all headers
|
|
96
|
+
*/
|
|
97
|
+
getAllHeaders() {
|
|
98
|
+
return this.request.headers || {};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
RequestService = _ts_decorate([
|
|
102
|
+
Injectable({
|
|
103
|
+
scope: Scope.REQUEST
|
|
104
|
+
}),
|
|
105
|
+
_ts_param(0, Inject(REQUEST)),
|
|
106
|
+
_ts_metadata("design:type", Function),
|
|
107
|
+
_ts_metadata("design:paramtypes", [
|
|
108
|
+
typeof FastifyRequest === "undefined" ? Object : FastifyRequest
|
|
109
|
+
])
|
|
110
|
+
], RequestService);
|
|
111
|
+
|
|
112
|
+
// src/request/request.module.ts
|
|
113
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
114
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
115
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
116
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
117
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
118
|
+
}
|
|
119
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
120
|
+
var RequestModule = class {
|
|
121
|
+
static {
|
|
122
|
+
__name(this, "RequestModule");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
RequestModule = _ts_decorate2([
|
|
126
|
+
Global(),
|
|
127
|
+
Module({
|
|
128
|
+
providers: [
|
|
129
|
+
RequestService
|
|
130
|
+
],
|
|
131
|
+
exports: [
|
|
132
|
+
RequestService
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
], RequestModule);
|
|
136
|
+
|
|
137
|
+
// src/auth/guards/vritti-auth.guard.ts
|
|
138
|
+
import { Injectable as Injectable3, Logger as Logger2, Scope as Scope2, UnauthorizedException } from "@nestjs/common";
|
|
139
|
+
import { ConfigService } from "@nestjs/config";
|
|
140
|
+
import { Reflector } from "@nestjs/core";
|
|
141
|
+
import { JwtService } from "@nestjs/jwt";
|
|
142
|
+
import * as jwt from "jsonwebtoken";
|
|
143
|
+
|
|
144
|
+
// src/database/services/primary-database.service.ts
|
|
145
|
+
import { Inject as Inject2, Injectable as Injectable2, InternalServerErrorException, Logger } from "@nestjs/common";
|
|
146
|
+
|
|
147
|
+
// src/database/constants.ts
|
|
148
|
+
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
149
|
+
|
|
150
|
+
// src/database/services/primary-database.service.ts
|
|
151
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
152
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
153
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
154
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
155
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
156
|
+
}
|
|
157
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
158
|
+
function _ts_metadata2(k, v) {
|
|
159
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
160
|
+
}
|
|
161
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
162
|
+
function _ts_param2(paramIndex, decorator) {
|
|
163
|
+
return function(target, key) {
|
|
164
|
+
decorator(target, key, paramIndex);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
__name(_ts_param2, "_ts_param");
|
|
29
168
|
var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
30
169
|
static {
|
|
31
170
|
__name(this, "PrimaryDatabaseService");
|
|
@@ -123,10 +262,13 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
123
262
|
id: tenantIdentifier
|
|
124
263
|
},
|
|
125
264
|
{
|
|
126
|
-
|
|
265
|
+
subdomain: tenantIdentifier
|
|
127
266
|
}
|
|
128
267
|
],
|
|
129
268
|
status: "ACTIVE"
|
|
269
|
+
},
|
|
270
|
+
include: {
|
|
271
|
+
databaseConfig: true
|
|
130
272
|
}
|
|
131
273
|
});
|
|
132
274
|
if (!tenant) {
|
|
@@ -136,16 +278,18 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
136
278
|
const info = {
|
|
137
279
|
id: tenant.id,
|
|
138
280
|
subdomain: tenant.subdomain,
|
|
139
|
-
type: tenant.
|
|
281
|
+
type: tenant.dbType,
|
|
140
282
|
status: tenant.status,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
283
|
+
// For SHARED tenants: schema name
|
|
284
|
+
schemaName: tenant.databaseConfig?.dbSchema || void 0,
|
|
285
|
+
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
286
|
+
databaseName: tenant.databaseConfig?.dbName || void 0,
|
|
287
|
+
databaseHost: tenant.databaseConfig?.dbHost || void 0,
|
|
288
|
+
databasePort: tenant.databaseConfig?.dbPort || void 0,
|
|
289
|
+
databaseUsername: tenant.databaseConfig?.dbUsername ? this.decrypt(tenant.databaseConfig.dbUsername) : void 0,
|
|
290
|
+
databasePassword: tenant.databaseConfig?.dbPassword ? this.decrypt(tenant.databaseConfig.dbPassword) : void 0,
|
|
291
|
+
databaseSslMode: tenant.databaseConfig?.dbSslMode || void 0,
|
|
292
|
+
connectionPoolSize: tenant.databaseConfig?.connectionPoolSize || void 0
|
|
149
293
|
};
|
|
150
294
|
this.cacheInfo(info);
|
|
151
295
|
return info;
|
|
@@ -221,24 +365,312 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
221
365
|
}
|
|
222
366
|
}
|
|
223
367
|
};
|
|
224
|
-
PrimaryDatabaseService =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
368
|
+
PrimaryDatabaseService = _ts_decorate3([
|
|
369
|
+
Injectable2(),
|
|
370
|
+
_ts_param2(0, Inject2(DATABASE_MODULE_OPTIONS)),
|
|
371
|
+
_ts_metadata2("design:type", Function),
|
|
372
|
+
_ts_metadata2("design:paramtypes", [
|
|
229
373
|
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
230
374
|
])
|
|
231
375
|
], PrimaryDatabaseService);
|
|
232
376
|
|
|
377
|
+
// src/auth/guards/vritti-auth.guard.ts
|
|
378
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
379
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
380
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
381
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
382
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
383
|
+
}
|
|
384
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
385
|
+
function _ts_metadata3(k, v) {
|
|
386
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
387
|
+
}
|
|
388
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
389
|
+
var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
390
|
+
static {
|
|
391
|
+
__name(this, "VrittiAuthGuard");
|
|
392
|
+
}
|
|
393
|
+
reflector;
|
|
394
|
+
configService;
|
|
395
|
+
jwtService;
|
|
396
|
+
primaryDatabase;
|
|
397
|
+
requestService;
|
|
398
|
+
logger = new Logger2(_VrittiAuthGuard.name);
|
|
399
|
+
constructor(reflector, configService, jwtService, primaryDatabase, requestService) {
|
|
400
|
+
this.reflector = reflector;
|
|
401
|
+
this.configService = configService;
|
|
402
|
+
this.jwtService = jwtService;
|
|
403
|
+
this.primaryDatabase = primaryDatabase;
|
|
404
|
+
this.requestService = requestService;
|
|
405
|
+
}
|
|
406
|
+
async canActivate(context) {
|
|
407
|
+
const request = context.switchToHttp().getRequest();
|
|
408
|
+
const isPublic = this.reflector.getAllAndOverride("isPublic", [
|
|
409
|
+
context.getHandler(),
|
|
410
|
+
context.getClass()
|
|
411
|
+
]);
|
|
412
|
+
if (isPublic) {
|
|
413
|
+
this.logger.debug("Public endpoint detected, skipping authentication");
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
const isOnboarding = this.reflector.getAllAndOverride("isOnboarding", [
|
|
417
|
+
context.getHandler(),
|
|
418
|
+
context.getClass()
|
|
419
|
+
]);
|
|
420
|
+
try {
|
|
421
|
+
const accessToken = this.requestService.getAccessToken();
|
|
422
|
+
if (!accessToken) {
|
|
423
|
+
this.logger.warn("Access token not found in Authorization header");
|
|
424
|
+
throw new UnauthorizedException("Access token not found");
|
|
425
|
+
}
|
|
426
|
+
const decodedToken = this.jwtService.decode(accessToken);
|
|
427
|
+
if (!decodedToken) {
|
|
428
|
+
this.logger.warn("Failed to decode access token");
|
|
429
|
+
throw new UnauthorizedException("Invalid token format");
|
|
430
|
+
}
|
|
431
|
+
if (isOnboarding) {
|
|
432
|
+
if (decodedToken.type !== "onboarding") {
|
|
433
|
+
this.logger.warn("Onboarding endpoint requires onboarding token");
|
|
434
|
+
throw new UnauthorizedException("This endpoint requires an onboarding token");
|
|
435
|
+
}
|
|
436
|
+
const validatedToken2 = this.validateAccessToken(accessToken);
|
|
437
|
+
this.logger.debug("Onboarding token validated successfully");
|
|
438
|
+
const userId2 = validatedToken2.userId;
|
|
439
|
+
request.user = {
|
|
440
|
+
id: userId2
|
|
441
|
+
};
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
if (decodedToken.type === "onboarding") {
|
|
445
|
+
this.logger.warn("Regular endpoint accessed with onboarding token");
|
|
446
|
+
throw new UnauthorizedException("Onboarding tokens cannot access this endpoint");
|
|
447
|
+
}
|
|
448
|
+
const validatedToken = this.validateAccessToken(accessToken);
|
|
449
|
+
this.logger.debug("Access token validated successfully");
|
|
450
|
+
const refreshToken = this.requestService.getRefreshToken();
|
|
451
|
+
if (!refreshToken) {
|
|
452
|
+
this.logger.warn("Refresh token (session-id) not found in cookies");
|
|
453
|
+
throw new UnauthorizedException("Refresh token not found");
|
|
454
|
+
}
|
|
455
|
+
this.validateRefreshToken(refreshToken);
|
|
456
|
+
this.logger.debug("Refresh token validated successfully");
|
|
457
|
+
const userId = validatedToken.userId;
|
|
458
|
+
request.user = {
|
|
459
|
+
id: userId
|
|
460
|
+
};
|
|
461
|
+
const tenantIdentifier = this.requestService.getTenantIdentifier();
|
|
462
|
+
if (!tenantIdentifier) {
|
|
463
|
+
this.logger.warn("Tenant identifier not found in request");
|
|
464
|
+
throw new UnauthorizedException("Tenant identifier not found");
|
|
465
|
+
}
|
|
466
|
+
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
467
|
+
if (tenantIdentifier === "cloud") {
|
|
468
|
+
this.logger.debug("Platform admin access detected, skipping tenant database validation");
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
472
|
+
if (!tenantInfo) {
|
|
473
|
+
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
474
|
+
throw new UnauthorizedException("Invalid tenant");
|
|
475
|
+
}
|
|
476
|
+
if (tenantInfo.status !== "ACTIVE") {
|
|
477
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
478
|
+
throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);
|
|
479
|
+
}
|
|
480
|
+
this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
481
|
+
return true;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
if (error instanceof UnauthorizedException) {
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
this.logger.error("Unexpected error in auth guard", error);
|
|
487
|
+
throw new UnauthorizedException("Authentication failed");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Validate access token with proper expiry checks
|
|
492
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
493
|
+
*/
|
|
494
|
+
validateAccessToken(token) {
|
|
495
|
+
try {
|
|
496
|
+
const decoded = this.jwtService.verify(token);
|
|
497
|
+
this.logger.debug(`Access token decoded for user: ${decoded.userId}`);
|
|
498
|
+
if (decoded.exp) {
|
|
499
|
+
const expiryTime = decoded.exp * 1e3;
|
|
500
|
+
const currentTime = Date.now();
|
|
501
|
+
const timeRemaining = expiryTime - currentTime;
|
|
502
|
+
this.logger.debug(`Access token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
503
|
+
}
|
|
504
|
+
return decoded;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (error instanceof UnauthorizedException) {
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
const jwtError = error;
|
|
510
|
+
if (jwtError?.name === "TokenExpiredError") {
|
|
511
|
+
this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);
|
|
512
|
+
throw new UnauthorizedException("Access token has expired");
|
|
513
|
+
}
|
|
514
|
+
if (jwtError?.name === "JsonWebTokenError") {
|
|
515
|
+
this.logger.warn(`Access token verification failed: ${jwtError?.message}`);
|
|
516
|
+
throw new UnauthorizedException("Invalid access token");
|
|
517
|
+
}
|
|
518
|
+
if (jwtError?.name === "NotBeforeError") {
|
|
519
|
+
this.logger.warn("Access token used before valid (nbf claim)");
|
|
520
|
+
throw new UnauthorizedException("Access token not yet valid");
|
|
521
|
+
}
|
|
522
|
+
this.logger.error("Unexpected error validating access token", error);
|
|
523
|
+
throw new UnauthorizedException("Access token validation failed");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Validate refresh token with proper expiry checks
|
|
528
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
529
|
+
*/
|
|
530
|
+
validateRefreshToken(token) {
|
|
531
|
+
const jwtSecret = this.configService.get("JWT_REFRESH_SECRET") || this.configService.get("JWT_SECRET");
|
|
532
|
+
this.validateRefreshTokenWithSecret(token, jwtSecret);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Helper to validate refresh token with specific secret
|
|
536
|
+
*/
|
|
537
|
+
validateRefreshTokenWithSecret(token, secret) {
|
|
538
|
+
if (!secret) {
|
|
539
|
+
this.logger.error("JWT secret not configured for refresh token validation");
|
|
540
|
+
throw new UnauthorizedException("Server configuration error");
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const decoded = jwt.verify(token, secret, {
|
|
544
|
+
algorithms: [
|
|
545
|
+
"HS256",
|
|
546
|
+
"HS512",
|
|
547
|
+
"RS256"
|
|
548
|
+
]
|
|
549
|
+
});
|
|
550
|
+
this.logger.debug(`Refresh token decoded for user: ${decoded.userId}`);
|
|
551
|
+
if (decoded.exp) {
|
|
552
|
+
const expiryTime = decoded.exp * 1e3;
|
|
553
|
+
const currentTime = Date.now();
|
|
554
|
+
if (currentTime > expiryTime) {
|
|
555
|
+
this.logger.warn("Refresh token has expired");
|
|
556
|
+
throw new UnauthorizedException("Refresh token has expired. Please login again");
|
|
557
|
+
}
|
|
558
|
+
const timeRemaining = expiryTime - currentTime;
|
|
559
|
+
this.logger.debug(`Refresh token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
560
|
+
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error instanceof UnauthorizedException) {
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
const jwtError = error;
|
|
566
|
+
if (jwtError?.name === "TokenExpiredError") {
|
|
567
|
+
this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);
|
|
568
|
+
throw new UnauthorizedException("Refresh token has expired. Please login again");
|
|
569
|
+
}
|
|
570
|
+
if (jwtError?.name === "JsonWebTokenError") {
|
|
571
|
+
this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);
|
|
572
|
+
throw new UnauthorizedException("Invalid refresh token");
|
|
573
|
+
}
|
|
574
|
+
if (jwtError?.name === "NotBeforeError") {
|
|
575
|
+
this.logger.warn("Refresh token used before valid (nbf claim)");
|
|
576
|
+
throw new UnauthorizedException("Refresh token not yet valid");
|
|
577
|
+
}
|
|
578
|
+
this.logger.error("Unexpected error validating refresh token", error);
|
|
579
|
+
throw new UnauthorizedException("Refresh token validation failed");
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
VrittiAuthGuard = _ts_decorate4([
|
|
584
|
+
Injectable3({
|
|
585
|
+
scope: Scope2.REQUEST
|
|
586
|
+
}),
|
|
587
|
+
_ts_metadata3("design:type", Function),
|
|
588
|
+
_ts_metadata3("design:paramtypes", [
|
|
589
|
+
typeof Reflector === "undefined" ? Object : Reflector,
|
|
590
|
+
typeof ConfigService === "undefined" ? Object : ConfigService,
|
|
591
|
+
typeof JwtService === "undefined" ? Object : JwtService,
|
|
592
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
593
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
594
|
+
])
|
|
595
|
+
], VrittiAuthGuard);
|
|
596
|
+
|
|
597
|
+
// src/auth/auth-config.module.ts
|
|
598
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
599
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
600
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
601
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
602
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
603
|
+
}
|
|
604
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
605
|
+
var AuthConfigModule = class _AuthConfigModule {
|
|
606
|
+
static {
|
|
607
|
+
__name(this, "AuthConfigModule");
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Register the auth module with async configuration
|
|
611
|
+
*
|
|
612
|
+
* This method:
|
|
613
|
+
* 1. Configures JwtModule with JWT_SECRET from ConfigService
|
|
614
|
+
* 2. Provides VrittiAuthGuard globally (applies to all routes)
|
|
615
|
+
* 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
|
|
616
|
+
*
|
|
617
|
+
* @returns Dynamic module configuration
|
|
618
|
+
*/
|
|
619
|
+
static forRootAsync() {
|
|
620
|
+
return {
|
|
621
|
+
module: _AuthConfigModule,
|
|
622
|
+
imports: [
|
|
623
|
+
ConfigModule,
|
|
624
|
+
RequestModule,
|
|
625
|
+
JwtModule.registerAsync({
|
|
626
|
+
imports: [
|
|
627
|
+
ConfigModule
|
|
628
|
+
],
|
|
629
|
+
inject: [
|
|
630
|
+
ConfigService2
|
|
631
|
+
],
|
|
632
|
+
useFactory: /* @__PURE__ */ __name((config) => ({
|
|
633
|
+
secret: config.get("JWT_SECRET"),
|
|
634
|
+
signOptions: {
|
|
635
|
+
algorithm: "HS256"
|
|
636
|
+
}
|
|
637
|
+
}), "useFactory")
|
|
638
|
+
})
|
|
639
|
+
],
|
|
640
|
+
providers: [
|
|
641
|
+
{
|
|
642
|
+
provide: APP_GUARD,
|
|
643
|
+
useClass: VrittiAuthGuard
|
|
644
|
+
}
|
|
645
|
+
],
|
|
646
|
+
exports: [
|
|
647
|
+
JwtModule
|
|
648
|
+
]
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
AuthConfigModule = _ts_decorate5([
|
|
653
|
+
Global2(),
|
|
654
|
+
Module2({})
|
|
655
|
+
], AuthConfigModule);
|
|
656
|
+
|
|
657
|
+
// src/database/database.module.ts
|
|
658
|
+
import { Global as Global3, Module as Module3 } from "@nestjs/common";
|
|
659
|
+
import { APP_INTERCEPTOR } from "@nestjs/core";
|
|
660
|
+
|
|
661
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
662
|
+
import { Injectable as Injectable5, Logger as Logger3, Scope as Scope4 } from "@nestjs/common";
|
|
663
|
+
import { tap } from "rxjs/operators";
|
|
664
|
+
|
|
233
665
|
// src/database/services/tenant-context.service.ts
|
|
234
|
-
import { Injectable as
|
|
235
|
-
function
|
|
666
|
+
import { Injectable as Injectable4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
667
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
236
668
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
237
669
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
238
670
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
239
671
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
240
672
|
}
|
|
241
|
-
__name(
|
|
673
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
242
674
|
var TenantContextService = class {
|
|
243
675
|
static {
|
|
244
676
|
__name(this, "TenantContextService");
|
|
@@ -269,7 +701,7 @@ var TenantContextService = class {
|
|
|
269
701
|
*/
|
|
270
702
|
getTenant() {
|
|
271
703
|
if (!this.tenantInfo) {
|
|
272
|
-
throw new
|
|
704
|
+
throw new UnauthorizedException2("Tenant context not set");
|
|
273
705
|
}
|
|
274
706
|
return this.tenantInfo;
|
|
275
707
|
}
|
|
@@ -310,71 +742,214 @@ var TenantContextService = class {
|
|
|
310
742
|
return this.tenantInfo?.subdomain ?? null;
|
|
311
743
|
}
|
|
312
744
|
};
|
|
313
|
-
TenantContextService =
|
|
314
|
-
|
|
315
|
-
scope:
|
|
745
|
+
TenantContextService = _ts_decorate6([
|
|
746
|
+
Injectable4({
|
|
747
|
+
scope: Scope3.REQUEST
|
|
316
748
|
})
|
|
317
749
|
], TenantContextService);
|
|
318
750
|
|
|
319
|
-
// src/database/
|
|
320
|
-
|
|
321
|
-
function _ts_decorate3(decorators, target, key, desc) {
|
|
751
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
752
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
322
753
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
323
754
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
324
755
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
325
756
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
326
757
|
}
|
|
327
|
-
__name(
|
|
328
|
-
function
|
|
758
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
759
|
+
function _ts_metadata4(k, v) {
|
|
329
760
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
330
761
|
}
|
|
331
|
-
__name(
|
|
332
|
-
|
|
333
|
-
return function(target, key) {
|
|
334
|
-
decorator(target, key, paramIndex);
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
__name(_ts_param2, "_ts_param");
|
|
338
|
-
var TenantDatabaseService = class _TenantDatabaseService {
|
|
762
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
763
|
+
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
339
764
|
static {
|
|
340
|
-
__name(this, "
|
|
765
|
+
__name(this, "MessageTenantContextInterceptor");
|
|
341
766
|
}
|
|
342
|
-
options;
|
|
343
767
|
tenantContext;
|
|
344
|
-
logger = new
|
|
345
|
-
|
|
346
|
-
clients = /* @__PURE__ */ new Map();
|
|
347
|
-
/** Track last usage time for idle connection cleanup */
|
|
348
|
-
clientLastUsed = /* @__PURE__ */ new Map();
|
|
349
|
-
/** Cleanup interval timer */
|
|
350
|
-
cleanupInterval;
|
|
351
|
-
constructor(options, tenantContext) {
|
|
352
|
-
this.options = options;
|
|
768
|
+
logger = new Logger3(_MessageTenantContextInterceptor.name);
|
|
769
|
+
constructor(tenantContext) {
|
|
353
770
|
this.tenantContext = tenantContext;
|
|
354
|
-
this.startConnectionCleaner();
|
|
355
771
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
772
|
+
intercept(context, next) {
|
|
773
|
+
const contextType = context.getType();
|
|
774
|
+
if (contextType === "rpc") {
|
|
775
|
+
const rpcContext = context.switchToRpc();
|
|
776
|
+
const payload = rpcContext.getData();
|
|
777
|
+
if (payload && payload.tenant) {
|
|
778
|
+
const tenant = payload.tenant;
|
|
779
|
+
this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
|
|
780
|
+
try {
|
|
781
|
+
this.tenantContext.setTenant(tenant);
|
|
782
|
+
this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
this.logger.error("Failed to set tenant context from message", error);
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
this.logger.warn("Message payload missing tenant information");
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return next.handle().pipe(tap({
|
|
791
|
+
next: /* @__PURE__ */ __name(() => {
|
|
792
|
+
this.cleanupContext();
|
|
793
|
+
}, "next"),
|
|
794
|
+
error: /* @__PURE__ */ __name(() => {
|
|
795
|
+
this.cleanupContext();
|
|
796
|
+
}, "error"),
|
|
797
|
+
complete: /* @__PURE__ */ __name(() => {
|
|
798
|
+
this.cleanupContext();
|
|
799
|
+
}, "complete")
|
|
800
|
+
}));
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Clean up tenant context after message is processed
|
|
804
|
+
*/
|
|
805
|
+
cleanupContext() {
|
|
806
|
+
if (this.tenantContext.hasTenant()) {
|
|
807
|
+
const tenant = this.tenantContext.getTenantIdSafe();
|
|
808
|
+
this.tenantContext.clearTenant();
|
|
809
|
+
this.logger.debug(`Cleaned up tenant context: ${tenant}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
MessageTenantContextInterceptor = _ts_decorate7([
|
|
814
|
+
Injectable5({
|
|
815
|
+
scope: Scope4.REQUEST
|
|
816
|
+
}),
|
|
817
|
+
_ts_metadata4("design:type", Function),
|
|
818
|
+
_ts_metadata4("design:paramtypes", [
|
|
819
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
820
|
+
])
|
|
821
|
+
], MessageTenantContextInterceptor);
|
|
822
|
+
|
|
823
|
+
// src/database/interceptors/tenant-context.interceptor.ts
|
|
824
|
+
import { Injectable as Injectable6, Logger as Logger4, Scope as Scope5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
|
|
825
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
826
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
827
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
828
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
829
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
830
|
+
}
|
|
831
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
832
|
+
function _ts_metadata5(k, v) {
|
|
833
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
834
|
+
}
|
|
835
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
836
|
+
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
837
|
+
static {
|
|
838
|
+
__name(this, "TenantContextInterceptor");
|
|
839
|
+
}
|
|
840
|
+
tenantContext;
|
|
841
|
+
primaryDatabase;
|
|
842
|
+
requestService;
|
|
843
|
+
logger = new Logger4(_TenantContextInterceptor.name);
|
|
844
|
+
constructor(tenantContext, primaryDatabase, requestService) {
|
|
845
|
+
this.tenantContext = tenantContext;
|
|
846
|
+
this.primaryDatabase = primaryDatabase;
|
|
847
|
+
this.requestService = requestService;
|
|
848
|
+
}
|
|
849
|
+
async intercept(context, next) {
|
|
850
|
+
const request = context.switchToHttp().getRequest();
|
|
851
|
+
this.logger.debug(`Processing request: ${request.method} ${request.url}`);
|
|
852
|
+
try {
|
|
853
|
+
const tenantIdentifier = this.requestService.getTenantIdentifier();
|
|
854
|
+
if (!tenantIdentifier) {
|
|
855
|
+
throw new UnauthorizedException3("Tenant identifier not found in request");
|
|
856
|
+
}
|
|
857
|
+
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
858
|
+
if (tenantIdentifier === "cloud") {
|
|
859
|
+
this.logger.log("Cloud platform access detected, skipping tenant context setup");
|
|
860
|
+
return next.handle();
|
|
861
|
+
}
|
|
862
|
+
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
863
|
+
if (!tenantInfo) {
|
|
864
|
+
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
865
|
+
throw new UnauthorizedException3("Invalid tenant");
|
|
866
|
+
}
|
|
867
|
+
if (tenantInfo.status !== "ACTIVE") {
|
|
868
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
869
|
+
throw new UnauthorizedException3(`Tenant is ${tenantInfo.status}`);
|
|
870
|
+
}
|
|
871
|
+
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
872
|
+
this.tenantContext.setTenant(tenantInfo);
|
|
873
|
+
request.tenant = tenantInfo;
|
|
874
|
+
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
this.logger.error("Failed to set tenant context", error);
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
879
|
+
return next.handle();
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
TenantContextInterceptor = _ts_decorate8([
|
|
883
|
+
Injectable6({
|
|
884
|
+
scope: Scope5.REQUEST
|
|
885
|
+
}),
|
|
886
|
+
_ts_metadata5("design:type", Function),
|
|
887
|
+
_ts_metadata5("design:paramtypes", [
|
|
888
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
889
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
890
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
891
|
+
])
|
|
892
|
+
], TenantContextInterceptor);
|
|
893
|
+
|
|
894
|
+
// src/database/services/tenant-database.service.ts
|
|
895
|
+
import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger5 } from "@nestjs/common";
|
|
896
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
897
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
898
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
899
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
900
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
901
|
+
}
|
|
902
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
903
|
+
function _ts_metadata6(k, v) {
|
|
904
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
905
|
+
}
|
|
906
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
907
|
+
function _ts_param3(paramIndex, decorator) {
|
|
908
|
+
return function(target, key) {
|
|
909
|
+
decorator(target, key, paramIndex);
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
__name(_ts_param3, "_ts_param");
|
|
913
|
+
var TenantDatabaseService = class _TenantDatabaseService {
|
|
914
|
+
static {
|
|
915
|
+
__name(this, "TenantDatabaseService");
|
|
916
|
+
}
|
|
917
|
+
options;
|
|
918
|
+
tenantContext;
|
|
919
|
+
logger = new Logger5(_TenantDatabaseService.name);
|
|
920
|
+
/** Connection pool: Map<cacheKey, DbClient> */
|
|
921
|
+
clients = /* @__PURE__ */ new Map();
|
|
922
|
+
/** Track last usage time for idle connection cleanup */
|
|
923
|
+
clientLastUsed = /* @__PURE__ */ new Map();
|
|
924
|
+
/** Cleanup interval timer */
|
|
925
|
+
cleanupInterval;
|
|
926
|
+
constructor(options, tenantContext) {
|
|
927
|
+
this.options = options;
|
|
928
|
+
this.tenantContext = tenantContext;
|
|
929
|
+
this.startConnectionCleaner();
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Get tenant-scoped database client for the current request/message
|
|
933
|
+
*
|
|
934
|
+
* This method:
|
|
935
|
+
* 1. Gets tenant info from TenantContextService
|
|
936
|
+
* 2. Builds a connection URL based on tenant type
|
|
937
|
+
* 3. Returns cached client if exists, otherwise creates new one
|
|
938
|
+
*
|
|
939
|
+
* @returns Promise<Database client instance>
|
|
940
|
+
* @throws UnauthorizedException if tenant context not set
|
|
941
|
+
* @throws InternalServerErrorException if connection fails
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
945
|
+
* const users = await dbClient.user.findMany();
|
|
946
|
+
*/
|
|
947
|
+
async getDbClient() {
|
|
948
|
+
const tenant = this.tenantContext.getTenant();
|
|
949
|
+
const cacheKey = this.buildCacheKey(tenant);
|
|
950
|
+
if (this.clients.has(cacheKey)) {
|
|
951
|
+
this.clientLastUsed.set(cacheKey, Date.now());
|
|
952
|
+
this.logger.debug(`Reusing cached connection: ${cacheKey}`);
|
|
378
953
|
return this.clients.get(cacheKey);
|
|
379
954
|
}
|
|
380
955
|
this.logger.log(`Creating new database connection: ${cacheKey}`);
|
|
@@ -497,87 +1072,116 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
497
1072
|
this.logger.log("All database connections closed");
|
|
498
1073
|
}
|
|
499
1074
|
};
|
|
500
|
-
TenantDatabaseService =
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1075
|
+
TenantDatabaseService = _ts_decorate9([
|
|
1076
|
+
Injectable7(),
|
|
1077
|
+
_ts_param3(0, Inject3(DATABASE_MODULE_OPTIONS)),
|
|
1078
|
+
_ts_metadata6("design:type", Function),
|
|
1079
|
+
_ts_metadata6("design:paramtypes", [
|
|
505
1080
|
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions,
|
|
506
1081
|
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
507
1082
|
])
|
|
508
1083
|
], TenantDatabaseService);
|
|
509
1084
|
|
|
510
1085
|
// src/database/database.module.ts
|
|
511
|
-
function
|
|
1086
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
512
1087
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
513
1088
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
514
1089
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
515
1090
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
516
1091
|
}
|
|
517
|
-
__name(
|
|
1092
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
518
1093
|
var DatabaseModule = class _DatabaseModule {
|
|
519
1094
|
static {
|
|
520
1095
|
__name(this, "DatabaseModule");
|
|
521
1096
|
}
|
|
522
1097
|
/**
|
|
523
|
-
*
|
|
1098
|
+
* Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
|
|
524
1099
|
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
1100
|
+
* This mode is for API Gateways that handle HTTP requests:
|
|
1101
|
+
* - Automatically registers TenantContextInterceptor
|
|
1102
|
+
* - Extracts tenant from subdomain or x-tenant-id header
|
|
1103
|
+
* - Queries primary database for tenant configuration
|
|
1104
|
+
* - Provides PrimaryDatabaseService for tenant lookup
|
|
1105
|
+
*
|
|
1106
|
+
* @param options Async configuration options
|
|
1107
|
+
* @returns Dynamic module configuration with HTTP interceptor
|
|
1108
|
+
*
|
|
1109
|
+
* @example
|
|
1110
|
+
* DatabaseModule.forServer({
|
|
1111
|
+
* inject: [ConfigService],
|
|
1112
|
+
* useFactory: (config: ConfigService) => ({
|
|
1113
|
+
* primaryDb: {
|
|
1114
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
1115
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
1116
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
1117
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
1118
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
1119
|
+
* },
|
|
1120
|
+
* prismaClientConstructor: PrismaClient,
|
|
1121
|
+
* }),
|
|
1122
|
+
* })
|
|
527
1123
|
*/
|
|
528
|
-
static
|
|
529
|
-
|
|
530
|
-
{
|
|
531
|
-
provide: DATABASE_MODULE_OPTIONS,
|
|
532
|
-
useValue: options
|
|
533
|
-
},
|
|
534
|
-
TenantDatabaseService,
|
|
535
|
-
TenantContextService,
|
|
536
|
-
PrimaryDatabaseService
|
|
537
|
-
];
|
|
538
|
-
return {
|
|
539
|
-
module: _DatabaseModule,
|
|
540
|
-
providers,
|
|
541
|
-
exports: [
|
|
542
|
-
TenantDatabaseService,
|
|
543
|
-
TenantContextService,
|
|
544
|
-
PrimaryDatabaseService
|
|
545
|
-
]
|
|
546
|
-
};
|
|
1124
|
+
static forServer(options) {
|
|
1125
|
+
return this.createDynamicModule(options, "gateway");
|
|
547
1126
|
}
|
|
548
1127
|
/**
|
|
549
|
-
*
|
|
1128
|
+
* Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
|
|
550
1129
|
*
|
|
551
|
-
*
|
|
1130
|
+
* This mode is for microservices that process messages from queues:
|
|
1131
|
+
* - Automatically registers MessageTenantContextInterceptor
|
|
1132
|
+
* - Extracts tenant from RabbitMQ message patterns
|
|
1133
|
+
* - No primary database needed (tenant comes from message context)
|
|
552
1134
|
*
|
|
553
1135
|
* @param options Async configuration options
|
|
554
|
-
* @returns Dynamic module configuration
|
|
1136
|
+
* @returns Dynamic module configuration with message interceptor
|
|
555
1137
|
*
|
|
556
1138
|
* @example
|
|
557
|
-
* DatabaseModule.
|
|
558
|
-
*
|
|
559
|
-
* useFactory:
|
|
560
|
-
* cloudDatabaseUrl: config.get('CLOUD_DATABASE_URL'),
|
|
1139
|
+
* DatabaseModule.forMicroservice({
|
|
1140
|
+
* inject: [ConfigService],
|
|
1141
|
+
* useFactory: (config: ConfigService) => ({
|
|
561
1142
|
* prismaClientConstructor: PrismaClient,
|
|
562
|
-
* tenantResolver: 'subdomain',
|
|
563
1143
|
* }),
|
|
564
|
-
* inject: [ConfigService],
|
|
565
1144
|
* })
|
|
566
1145
|
*/
|
|
567
|
-
static
|
|
1146
|
+
static forMicroservice(options) {
|
|
1147
|
+
return this.createDynamicModule(options, "microservice");
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Internal helper to create dynamic module with conditional interceptor registration
|
|
1151
|
+
*
|
|
1152
|
+
* @param options Configuration options
|
|
1153
|
+
* @param mode Mode of operation (gateway or microservice)
|
|
1154
|
+
* @returns Dynamic module configuration
|
|
1155
|
+
*/
|
|
1156
|
+
static createDynamicModule(options, mode) {
|
|
568
1157
|
const asyncProvider = {
|
|
569
1158
|
provide: DATABASE_MODULE_OPTIONS,
|
|
570
1159
|
useFactory: options.useFactory,
|
|
571
1160
|
inject: options.inject || []
|
|
572
1161
|
};
|
|
1162
|
+
const providers = [
|
|
1163
|
+
asyncProvider,
|
|
1164
|
+
TenantContextService,
|
|
1165
|
+
PrimaryDatabaseService,
|
|
1166
|
+
TenantDatabaseService
|
|
1167
|
+
];
|
|
1168
|
+
if (mode === "gateway") {
|
|
1169
|
+
providers.push({
|
|
1170
|
+
provide: APP_INTERCEPTOR,
|
|
1171
|
+
useClass: TenantContextInterceptor
|
|
1172
|
+
});
|
|
1173
|
+
} else {
|
|
1174
|
+
providers.push({
|
|
1175
|
+
provide: APP_INTERCEPTOR,
|
|
1176
|
+
useClass: MessageTenantContextInterceptor
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
573
1179
|
return {
|
|
574
1180
|
module: _DatabaseModule,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
TenantContextService,
|
|
578
|
-
PrimaryDatabaseService,
|
|
579
|
-
TenantDatabaseService
|
|
1181
|
+
imports: [
|
|
1182
|
+
RequestModule
|
|
580
1183
|
],
|
|
1184
|
+
providers,
|
|
581
1185
|
exports: [
|
|
582
1186
|
TenantDatabaseService,
|
|
583
1187
|
TenantContextService,
|
|
@@ -587,217 +1191,11 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
587
1191
|
};
|
|
588
1192
|
}
|
|
589
1193
|
};
|
|
590
|
-
DatabaseModule =
|
|
591
|
-
|
|
592
|
-
|
|
1194
|
+
DatabaseModule = _ts_decorate10([
|
|
1195
|
+
Global3(),
|
|
1196
|
+
Module3({})
|
|
593
1197
|
], DatabaseModule);
|
|
594
1198
|
|
|
595
|
-
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
596
|
-
import { Injectable as Injectable4, Logger as Logger3, Scope as Scope2 } from "@nestjs/common";
|
|
597
|
-
import { tap } from "rxjs/operators";
|
|
598
|
-
function _ts_decorate5(decorators, target, key, desc) {
|
|
599
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
600
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
601
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
602
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
603
|
-
}
|
|
604
|
-
__name(_ts_decorate5, "_ts_decorate");
|
|
605
|
-
function _ts_metadata3(k, v) {
|
|
606
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
607
|
-
}
|
|
608
|
-
__name(_ts_metadata3, "_ts_metadata");
|
|
609
|
-
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
610
|
-
static {
|
|
611
|
-
__name(this, "MessageTenantContextInterceptor");
|
|
612
|
-
}
|
|
613
|
-
tenantContext;
|
|
614
|
-
logger = new Logger3(_MessageTenantContextInterceptor.name);
|
|
615
|
-
constructor(tenantContext) {
|
|
616
|
-
this.tenantContext = tenantContext;
|
|
617
|
-
}
|
|
618
|
-
intercept(context, next) {
|
|
619
|
-
const contextType = context.getType();
|
|
620
|
-
if (contextType === "rpc") {
|
|
621
|
-
const rpcContext = context.switchToRpc();
|
|
622
|
-
const payload = rpcContext.getData();
|
|
623
|
-
if (payload && payload.tenant) {
|
|
624
|
-
const tenant = payload.tenant;
|
|
625
|
-
this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
|
|
626
|
-
try {
|
|
627
|
-
this.tenantContext.setTenant(tenant);
|
|
628
|
-
this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
|
|
629
|
-
} catch (error) {
|
|
630
|
-
this.logger.error("Failed to set tenant context from message", error);
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
this.logger.warn("Message payload missing tenant information");
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return next.handle().pipe(tap({
|
|
637
|
-
next: /* @__PURE__ */ __name(() => {
|
|
638
|
-
this.cleanupContext();
|
|
639
|
-
}, "next"),
|
|
640
|
-
error: /* @__PURE__ */ __name(() => {
|
|
641
|
-
this.cleanupContext();
|
|
642
|
-
}, "error"),
|
|
643
|
-
complete: /* @__PURE__ */ __name(() => {
|
|
644
|
-
this.cleanupContext();
|
|
645
|
-
}, "complete")
|
|
646
|
-
}));
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Clean up tenant context after message is processed
|
|
650
|
-
*/
|
|
651
|
-
cleanupContext() {
|
|
652
|
-
if (this.tenantContext.hasTenant()) {
|
|
653
|
-
const tenant = this.tenantContext.getTenantIdSafe();
|
|
654
|
-
this.tenantContext.clearTenant();
|
|
655
|
-
this.logger.debug(`Cleaned up tenant context: ${tenant}`);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
MessageTenantContextInterceptor = _ts_decorate5([
|
|
660
|
-
Injectable4({
|
|
661
|
-
scope: Scope2.REQUEST
|
|
662
|
-
}),
|
|
663
|
-
_ts_metadata3("design:type", Function),
|
|
664
|
-
_ts_metadata3("design:paramtypes", [
|
|
665
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
666
|
-
])
|
|
667
|
-
], MessageTenantContextInterceptor);
|
|
668
|
-
|
|
669
|
-
// src/database/interceptors/tenant-context.interceptor.ts
|
|
670
|
-
import { Inject as Inject3, Injectable as Injectable5, Logger as Logger4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
671
|
-
|
|
672
|
-
// src/database/utils/subdomain-parser.util.ts
|
|
673
|
-
function extractSubdomain(host) {
|
|
674
|
-
if (!host) {
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
const hostname = host.split(":")[0];
|
|
678
|
-
if (!hostname) {
|
|
679
|
-
return null;
|
|
680
|
-
}
|
|
681
|
-
const parts = hostname.split(".");
|
|
682
|
-
if (parts.length < 3) {
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
685
|
-
return parts[0] ?? null;
|
|
686
|
-
}
|
|
687
|
-
__name(extractSubdomain, "extractSubdomain");
|
|
688
|
-
|
|
689
|
-
// src/database/interceptors/tenant-context.interceptor.ts
|
|
690
|
-
function _ts_decorate6(decorators, target, key, desc) {
|
|
691
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
692
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
693
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
694
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
695
|
-
}
|
|
696
|
-
__name(_ts_decorate6, "_ts_decorate");
|
|
697
|
-
function _ts_metadata4(k, v) {
|
|
698
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
699
|
-
}
|
|
700
|
-
__name(_ts_metadata4, "_ts_metadata");
|
|
701
|
-
function _ts_param3(paramIndex, decorator) {
|
|
702
|
-
return function(target, key) {
|
|
703
|
-
decorator(target, key, paramIndex);
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
__name(_ts_param3, "_ts_param");
|
|
707
|
-
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
708
|
-
static {
|
|
709
|
-
__name(this, "TenantContextInterceptor");
|
|
710
|
-
}
|
|
711
|
-
tenantContext;
|
|
712
|
-
primaryDatabase;
|
|
713
|
-
options;
|
|
714
|
-
logger = new Logger4(_TenantContextInterceptor.name);
|
|
715
|
-
constructor(tenantContext, primaryDatabase, options) {
|
|
716
|
-
this.tenantContext = tenantContext;
|
|
717
|
-
this.primaryDatabase = primaryDatabase;
|
|
718
|
-
this.options = options;
|
|
719
|
-
}
|
|
720
|
-
async intercept(context, next) {
|
|
721
|
-
const request = context.switchToHttp().getRequest();
|
|
722
|
-
this.logger.debug(`Processing request: ${request.method} ${request.url}`);
|
|
723
|
-
try {
|
|
724
|
-
const tenantIdentifier = this.extractTenantIdentifier(request);
|
|
725
|
-
if (!tenantIdentifier) {
|
|
726
|
-
throw new UnauthorizedException2("Tenant identifier not found in request");
|
|
727
|
-
}
|
|
728
|
-
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
729
|
-
if (tenantIdentifier === "cloud") {
|
|
730
|
-
this.logger.log("Cloud platform access detected, skipping tenant context setup");
|
|
731
|
-
return next.handle();
|
|
732
|
-
}
|
|
733
|
-
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
734
|
-
if (!tenantInfo) {
|
|
735
|
-
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
736
|
-
throw new UnauthorizedException2("Invalid tenant");
|
|
737
|
-
}
|
|
738
|
-
if (tenantInfo.status !== "ACTIVE") {
|
|
739
|
-
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
740
|
-
throw new UnauthorizedException2(`Tenant is ${tenantInfo.status}`);
|
|
741
|
-
}
|
|
742
|
-
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
743
|
-
this.tenantContext.setTenant(tenantInfo);
|
|
744
|
-
request.tenant = tenantInfo;
|
|
745
|
-
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
746
|
-
} catch (error) {
|
|
747
|
-
this.logger.error("Failed to set tenant context", error);
|
|
748
|
-
throw error;
|
|
749
|
-
}
|
|
750
|
-
return next.handle();
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Extract tenant identifier: tries subdomain first, then falls back to header
|
|
754
|
-
*/
|
|
755
|
-
extractTenantIdentifier(request) {
|
|
756
|
-
let tenantIdentifier = this.extractFromSubdomain(request);
|
|
757
|
-
if (!tenantIdentifier) {
|
|
758
|
-
tenantIdentifier = this.extractFromHeader(request);
|
|
759
|
-
if (tenantIdentifier) {
|
|
760
|
-
this.logger.debug("Using tenant from header (subdomain not found)");
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return tenantIdentifier;
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Extract tenant from subdomain
|
|
767
|
-
* @example acme.vritti.com → 'acme'
|
|
768
|
-
*/
|
|
769
|
-
extractFromSubdomain(request) {
|
|
770
|
-
if (request.subdomain) {
|
|
771
|
-
return request.subdomain;
|
|
772
|
-
}
|
|
773
|
-
const host = request.headers?.host || request.hostname;
|
|
774
|
-
return extractSubdomain(host);
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Extract tenant from HTTP headers
|
|
778
|
-
* Checks x-tenant-id and x-subdomain headers
|
|
779
|
-
*/
|
|
780
|
-
extractFromHeader(request) {
|
|
781
|
-
const getHeader = /* @__PURE__ */ __name((key) => {
|
|
782
|
-
const value = request.headers?.[key];
|
|
783
|
-
return Array.isArray(value) ? value[0] : value;
|
|
784
|
-
}, "getHeader");
|
|
785
|
-
return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
TenantContextInterceptor = _ts_decorate6([
|
|
789
|
-
Injectable5({
|
|
790
|
-
scope: Scope3.REQUEST
|
|
791
|
-
}),
|
|
792
|
-
_ts_param3(2, Inject3(DATABASE_MODULE_OPTIONS)),
|
|
793
|
-
_ts_metadata4("design:type", Function),
|
|
794
|
-
_ts_metadata4("design:paramtypes", [
|
|
795
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
796
|
-
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
797
|
-
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
798
|
-
])
|
|
799
|
-
], TenantContextInterceptor);
|
|
800
|
-
|
|
801
1199
|
// src/database/decorators/tenant.decorator.ts
|
|
802
1200
|
import { createParamDecorator } from "@nestjs/common";
|
|
803
1201
|
var Tenant = createParamDecorator((data, ctx) => {
|
|
@@ -808,14 +1206,23 @@ var Tenant = createParamDecorator((data, ctx) => {
|
|
|
808
1206
|
}
|
|
809
1207
|
return tenantContext.getTenant();
|
|
810
1208
|
});
|
|
1209
|
+
|
|
1210
|
+
// src/auth/decorators/onboarding.decorator.ts
|
|
1211
|
+
import { SetMetadata } from "@nestjs/common";
|
|
1212
|
+
var Onboarding = /* @__PURE__ */ __name(() => SetMetadata("isOnboarding", true), "Onboarding");
|
|
1213
|
+
|
|
1214
|
+
// src/auth/decorators/public.decorator.ts
|
|
1215
|
+
import { SetMetadata as SetMetadata2 } from "@nestjs/common";
|
|
1216
|
+
var Public = /* @__PURE__ */ __name(() => SetMetadata2("isPublic", true), "Public");
|
|
811
1217
|
export {
|
|
1218
|
+
AuthConfigModule,
|
|
812
1219
|
DatabaseModule,
|
|
813
|
-
|
|
1220
|
+
Onboarding,
|
|
814
1221
|
PrimaryDatabaseService,
|
|
1222
|
+
Public,
|
|
815
1223
|
Tenant,
|
|
816
|
-
TenantContextInterceptor,
|
|
817
1224
|
TenantContextService,
|
|
818
1225
|
TenantDatabaseService,
|
|
819
|
-
|
|
1226
|
+
VrittiAuthGuard
|
|
820
1227
|
};
|
|
821
1228
|
//# sourceMappingURL=index.js.map
|