@vritti/api-sdk 0.0.2 → 0.0.5
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 +617 -43
- package/dist/index.cjs +1503 -312
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +963 -169
- package/dist/index.d.ts +963 -169
- package/dist/index.js +1484 -309
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
9
|
var __export = (target, all) => {
|
|
@@ -16,30 +18,48 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
|
|
21
31
|
// src/index.ts
|
|
22
32
|
var index_exports = {};
|
|
23
33
|
__export(index_exports, {
|
|
34
|
+
AuthConfigModule: () => AuthConfigModule,
|
|
35
|
+
CsrfGuard: () => CsrfGuard,
|
|
24
36
|
DatabaseModule: () => DatabaseModule,
|
|
25
|
-
|
|
37
|
+
HttpExceptionFilter: () => HttpExceptionFilter,
|
|
38
|
+
HttpModule: () => HttpModule,
|
|
39
|
+
Onboarding: () => Onboarding,
|
|
40
|
+
PrimaryBaseRepository: () => PrimaryBaseRepository,
|
|
26
41
|
PrimaryDatabaseService: () => PrimaryDatabaseService,
|
|
42
|
+
Public: () => Public,
|
|
27
43
|
Tenant: () => Tenant,
|
|
28
|
-
|
|
44
|
+
TenantBaseRepository: () => TenantBaseRepository,
|
|
29
45
|
TenantContextService: () => TenantContextService,
|
|
30
46
|
TenantDatabaseService: () => TenantDatabaseService,
|
|
31
|
-
|
|
47
|
+
VrittiAuthGuard: () => VrittiAuthGuard
|
|
32
48
|
});
|
|
33
49
|
module.exports = __toCommonJS(index_exports);
|
|
34
50
|
|
|
35
|
-
// src/
|
|
36
|
-
var
|
|
51
|
+
// src/auth/auth-config.module.ts
|
|
52
|
+
var import_common5 = require("@nestjs/common");
|
|
53
|
+
var import_config2 = require("@nestjs/config");
|
|
54
|
+
var import_core3 = require("@nestjs/core");
|
|
55
|
+
var import_jwt2 = require("@nestjs/jwt");
|
|
37
56
|
|
|
38
|
-
// src/
|
|
39
|
-
var
|
|
57
|
+
// src/request/request.module.ts
|
|
58
|
+
var import_common2 = require("@nestjs/common");
|
|
40
59
|
|
|
41
|
-
// src/
|
|
60
|
+
// src/request/services/request.service.ts
|
|
42
61
|
var import_common = require("@nestjs/common");
|
|
62
|
+
var import_core = require("@nestjs/core");
|
|
43
63
|
function _ts_decorate(decorators, target, key, desc) {
|
|
44
64
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
45
65
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -57,12 +77,147 @@ function _ts_param(paramIndex, decorator) {
|
|
|
57
77
|
};
|
|
58
78
|
}
|
|
59
79
|
__name(_ts_param, "_ts_param");
|
|
80
|
+
var RequestService = class {
|
|
81
|
+
static {
|
|
82
|
+
__name(this, "RequestService");
|
|
83
|
+
}
|
|
84
|
+
request;
|
|
85
|
+
constructor(request) {
|
|
86
|
+
this.request = request;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract tenant identifier from request headers
|
|
90
|
+
* Priority: x-tenant-id > x-subdomain
|
|
91
|
+
* @returns Tenant identifier or null if not found
|
|
92
|
+
*/
|
|
93
|
+
getTenantIdentifier() {
|
|
94
|
+
const getHeader = /* @__PURE__ */ __name((key) => {
|
|
95
|
+
const value = this.request.headers?.[key];
|
|
96
|
+
return Array.isArray(value) ? value[0] : value;
|
|
97
|
+
}, "getHeader");
|
|
98
|
+
return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extract access token from Authorization header
|
|
102
|
+
* Format: "Bearer <token>"
|
|
103
|
+
* @returns Access token or null if not found
|
|
104
|
+
*/
|
|
105
|
+
getAccessToken() {
|
|
106
|
+
const authHeader = this.request.headers?.authorization;
|
|
107
|
+
if (!authHeader) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const [type, token] = authHeader.split(" ") ?? [];
|
|
111
|
+
return type === "Bearer" && token ? token : null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Extract refresh token from session-id cookie
|
|
115
|
+
* Cookie name: session-id
|
|
116
|
+
* @returns Refresh token or null if not found
|
|
117
|
+
*/
|
|
118
|
+
getRefreshToken() {
|
|
119
|
+
try {
|
|
120
|
+
const cookies = this.request.cookies;
|
|
121
|
+
if (cookies && typeof cookies === "object") {
|
|
122
|
+
const sessionId = cookies["session-id"];
|
|
123
|
+
if (sessionId) {
|
|
124
|
+
return sessionId;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get a specific header value
|
|
134
|
+
* @param key Header key
|
|
135
|
+
* @returns Header value (string, array, or undefined)
|
|
136
|
+
*/
|
|
137
|
+
getHeader(key) {
|
|
138
|
+
return this.request.headers?.[key];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get all headers
|
|
142
|
+
* @returns Record of all headers
|
|
143
|
+
*/
|
|
144
|
+
getAllHeaders() {
|
|
145
|
+
return this.request.headers || {};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
RequestService = _ts_decorate([
|
|
149
|
+
(0, import_common.Injectable)({
|
|
150
|
+
scope: import_common.Scope.REQUEST
|
|
151
|
+
}),
|
|
152
|
+
_ts_param(0, (0, import_common.Inject)(import_core.REQUEST)),
|
|
153
|
+
_ts_metadata("design:type", Function),
|
|
154
|
+
_ts_metadata("design:paramtypes", [
|
|
155
|
+
typeof FastifyRequest === "undefined" ? Object : FastifyRequest
|
|
156
|
+
])
|
|
157
|
+
], RequestService);
|
|
158
|
+
|
|
159
|
+
// src/request/request.module.ts
|
|
160
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
161
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
162
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
163
|
+
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;
|
|
164
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
165
|
+
}
|
|
166
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
167
|
+
var RequestModule = class {
|
|
168
|
+
static {
|
|
169
|
+
__name(this, "RequestModule");
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
RequestModule = _ts_decorate2([
|
|
173
|
+
(0, import_common2.Global)(),
|
|
174
|
+
(0, import_common2.Module)({
|
|
175
|
+
providers: [
|
|
176
|
+
RequestService
|
|
177
|
+
],
|
|
178
|
+
exports: [
|
|
179
|
+
RequestService
|
|
180
|
+
]
|
|
181
|
+
})
|
|
182
|
+
], RequestModule);
|
|
183
|
+
|
|
184
|
+
// src/auth/guards/vritti-auth.guard.ts
|
|
185
|
+
var import_common4 = require("@nestjs/common");
|
|
186
|
+
var import_config = require("@nestjs/config");
|
|
187
|
+
var import_core2 = require("@nestjs/core");
|
|
188
|
+
var import_jwt = require("@nestjs/jwt");
|
|
189
|
+
var jwt = __toESM(require("jsonwebtoken"), 1);
|
|
190
|
+
|
|
191
|
+
// src/database/services/primary-database.service.ts
|
|
192
|
+
var import_common3 = require("@nestjs/common");
|
|
193
|
+
|
|
194
|
+
// src/database/constants.ts
|
|
195
|
+
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
196
|
+
|
|
197
|
+
// src/database/services/primary-database.service.ts
|
|
198
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
199
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
200
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
201
|
+
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;
|
|
202
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
203
|
+
}
|
|
204
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
205
|
+
function _ts_metadata2(k, v) {
|
|
206
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
207
|
+
}
|
|
208
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
209
|
+
function _ts_param2(paramIndex, decorator) {
|
|
210
|
+
return function(target, key) {
|
|
211
|
+
decorator(target, key, paramIndex);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
__name(_ts_param2, "_ts_param");
|
|
60
215
|
var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
61
216
|
static {
|
|
62
217
|
__name(this, "PrimaryDatabaseService");
|
|
63
218
|
}
|
|
64
219
|
options;
|
|
65
|
-
logger = new
|
|
220
|
+
logger = new import_common3.Logger(_PrimaryDatabaseService.name);
|
|
66
221
|
/** Primary database client for querying tenant registry */
|
|
67
222
|
primaryDbClient;
|
|
68
223
|
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
@@ -100,7 +255,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
100
255
|
this.logger.log("Connected to primary database (tenant registry)");
|
|
101
256
|
} catch (error) {
|
|
102
257
|
this.logger.error("Failed to connect to primary database", error);
|
|
103
|
-
throw new
|
|
258
|
+
throw new import_common3.InternalServerErrorException("Failed to initialize tenant registry");
|
|
104
259
|
}
|
|
105
260
|
}
|
|
106
261
|
/**
|
|
@@ -154,10 +309,13 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
154
309
|
id: tenantIdentifier
|
|
155
310
|
},
|
|
156
311
|
{
|
|
157
|
-
|
|
312
|
+
subdomain: tenantIdentifier
|
|
158
313
|
}
|
|
159
314
|
],
|
|
160
315
|
status: "ACTIVE"
|
|
316
|
+
},
|
|
317
|
+
include: {
|
|
318
|
+
databaseConfig: true
|
|
161
319
|
}
|
|
162
320
|
});
|
|
163
321
|
if (!tenant) {
|
|
@@ -167,22 +325,24 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
167
325
|
const info = {
|
|
168
326
|
id: tenant.id,
|
|
169
327
|
subdomain: tenant.subdomain,
|
|
170
|
-
type: tenant.
|
|
328
|
+
type: tenant.dbType,
|
|
171
329
|
status: tenant.status,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
330
|
+
// For SHARED tenants: schema name
|
|
331
|
+
schemaName: tenant.databaseConfig?.dbSchema || void 0,
|
|
332
|
+
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
333
|
+
databaseName: tenant.databaseConfig?.dbName || void 0,
|
|
334
|
+
databaseHost: tenant.databaseConfig?.dbHost || void 0,
|
|
335
|
+
databasePort: tenant.databaseConfig?.dbPort || void 0,
|
|
336
|
+
databaseUsername: tenant.databaseConfig?.dbUsername ? this.decrypt(tenant.databaseConfig.dbUsername) : void 0,
|
|
337
|
+
databasePassword: tenant.databaseConfig?.dbPassword ? this.decrypt(tenant.databaseConfig.dbPassword) : void 0,
|
|
338
|
+
databaseSslMode: tenant.databaseConfig?.dbSslMode || void 0,
|
|
339
|
+
connectionPoolSize: tenant.databaseConfig?.connectionPoolSize || void 0
|
|
180
340
|
};
|
|
181
341
|
this.cacheInfo(info);
|
|
182
342
|
return info;
|
|
183
343
|
} catch (error) {
|
|
184
344
|
this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);
|
|
185
|
-
throw new
|
|
345
|
+
throw new import_common3.InternalServerErrorException("Failed to resolve tenant");
|
|
186
346
|
}
|
|
187
347
|
}
|
|
188
348
|
/**
|
|
@@ -221,16 +381,15 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
221
381
|
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
222
382
|
}
|
|
223
383
|
/**
|
|
224
|
-
* Get
|
|
225
|
-
*
|
|
226
|
-
* This is useful for platform admin operations (creating tenants, billing, etc.)
|
|
384
|
+
* Get the Prisma client for the primary database.
|
|
385
|
+
* This is a synchronous property that returns the initialized Prisma client.
|
|
227
386
|
*
|
|
228
387
|
* @returns Primary database client instance
|
|
229
388
|
* @throws Error if primary database client is not initialized
|
|
230
389
|
*/
|
|
231
|
-
|
|
390
|
+
get prismaClient() {
|
|
232
391
|
if (!this.primaryDbClient) {
|
|
233
|
-
throw new Error("Primary database client not initialized
|
|
392
|
+
throw new Error("Primary database client not initialized");
|
|
234
393
|
}
|
|
235
394
|
return this.primaryDbClient;
|
|
236
395
|
}
|
|
@@ -252,24 +411,312 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
252
411
|
}
|
|
253
412
|
}
|
|
254
413
|
};
|
|
255
|
-
PrimaryDatabaseService =
|
|
256
|
-
(0,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
414
|
+
PrimaryDatabaseService = _ts_decorate3([
|
|
415
|
+
(0, import_common3.Injectable)(),
|
|
416
|
+
_ts_param2(0, (0, import_common3.Inject)(DATABASE_MODULE_OPTIONS)),
|
|
417
|
+
_ts_metadata2("design:type", Function),
|
|
418
|
+
_ts_metadata2("design:paramtypes", [
|
|
260
419
|
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
261
420
|
])
|
|
262
421
|
], PrimaryDatabaseService);
|
|
263
422
|
|
|
423
|
+
// src/auth/guards/vritti-auth.guard.ts
|
|
424
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
425
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
426
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
427
|
+
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;
|
|
428
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
429
|
+
}
|
|
430
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
431
|
+
function _ts_metadata3(k, v) {
|
|
432
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
433
|
+
}
|
|
434
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
435
|
+
var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
436
|
+
static {
|
|
437
|
+
__name(this, "VrittiAuthGuard");
|
|
438
|
+
}
|
|
439
|
+
reflector;
|
|
440
|
+
configService;
|
|
441
|
+
jwtService;
|
|
442
|
+
primaryDatabase;
|
|
443
|
+
requestService;
|
|
444
|
+
logger = new import_common4.Logger(_VrittiAuthGuard.name);
|
|
445
|
+
constructor(reflector, configService, jwtService, primaryDatabase, requestService) {
|
|
446
|
+
this.reflector = reflector;
|
|
447
|
+
this.configService = configService;
|
|
448
|
+
this.jwtService = jwtService;
|
|
449
|
+
this.primaryDatabase = primaryDatabase;
|
|
450
|
+
this.requestService = requestService;
|
|
451
|
+
}
|
|
452
|
+
async canActivate(context) {
|
|
453
|
+
const request = context.switchToHttp().getRequest();
|
|
454
|
+
const isPublic = this.reflector.getAllAndOverride("isPublic", [
|
|
455
|
+
context.getHandler(),
|
|
456
|
+
context.getClass()
|
|
457
|
+
]);
|
|
458
|
+
if (isPublic) {
|
|
459
|
+
this.logger.debug("Public endpoint detected, skipping authentication");
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
const isOnboarding = this.reflector.getAllAndOverride("isOnboarding", [
|
|
463
|
+
context.getHandler(),
|
|
464
|
+
context.getClass()
|
|
465
|
+
]);
|
|
466
|
+
try {
|
|
467
|
+
const accessToken = this.requestService.getAccessToken();
|
|
468
|
+
if (!accessToken) {
|
|
469
|
+
this.logger.warn("Access token not found in Authorization header");
|
|
470
|
+
throw new import_common4.UnauthorizedException("Access token not found");
|
|
471
|
+
}
|
|
472
|
+
const decodedToken = this.jwtService.decode(accessToken);
|
|
473
|
+
if (!decodedToken) {
|
|
474
|
+
this.logger.warn("Failed to decode access token");
|
|
475
|
+
throw new import_common4.UnauthorizedException("Invalid token format");
|
|
476
|
+
}
|
|
477
|
+
if (isOnboarding) {
|
|
478
|
+
if (decodedToken.type !== "onboarding") {
|
|
479
|
+
this.logger.warn("Onboarding endpoint requires onboarding token");
|
|
480
|
+
throw new import_common4.UnauthorizedException("This endpoint requires an onboarding token");
|
|
481
|
+
}
|
|
482
|
+
const validatedToken2 = this.validateAccessToken(accessToken);
|
|
483
|
+
this.logger.debug("Onboarding token validated successfully");
|
|
484
|
+
const userId2 = validatedToken2.userId;
|
|
485
|
+
request.user = {
|
|
486
|
+
id: userId2
|
|
487
|
+
};
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
if (decodedToken.type === "onboarding") {
|
|
491
|
+
this.logger.warn("Regular endpoint accessed with onboarding token");
|
|
492
|
+
throw new import_common4.UnauthorizedException("Onboarding tokens cannot access this endpoint");
|
|
493
|
+
}
|
|
494
|
+
const validatedToken = this.validateAccessToken(accessToken);
|
|
495
|
+
this.logger.debug("Access token validated successfully");
|
|
496
|
+
const refreshToken = this.requestService.getRefreshToken();
|
|
497
|
+
if (!refreshToken) {
|
|
498
|
+
this.logger.warn("Refresh token (session-id) not found in cookies");
|
|
499
|
+
throw new import_common4.UnauthorizedException("Refresh token not found");
|
|
500
|
+
}
|
|
501
|
+
this.validateRefreshToken(refreshToken);
|
|
502
|
+
this.logger.debug("Refresh token validated successfully");
|
|
503
|
+
const userId = validatedToken.userId;
|
|
504
|
+
request.user = {
|
|
505
|
+
id: userId
|
|
506
|
+
};
|
|
507
|
+
const tenantIdentifier = this.requestService.getTenantIdentifier();
|
|
508
|
+
if (!tenantIdentifier) {
|
|
509
|
+
this.logger.warn("Tenant identifier not found in request");
|
|
510
|
+
throw new import_common4.UnauthorizedException("Tenant identifier not found");
|
|
511
|
+
}
|
|
512
|
+
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
513
|
+
if (tenantIdentifier === "cloud") {
|
|
514
|
+
this.logger.debug("Platform admin access detected, skipping tenant database validation");
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
518
|
+
if (!tenantInfo) {
|
|
519
|
+
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
520
|
+
throw new import_common4.UnauthorizedException("Invalid tenant");
|
|
521
|
+
}
|
|
522
|
+
if (tenantInfo.status !== "ACTIVE") {
|
|
523
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
524
|
+
throw new import_common4.UnauthorizedException(`Tenant is ${tenantInfo.status}`);
|
|
525
|
+
}
|
|
526
|
+
this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
527
|
+
return true;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (error instanceof import_common4.UnauthorizedException) {
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
this.logger.error("Unexpected error in auth guard", error);
|
|
533
|
+
throw new import_common4.UnauthorizedException("Authentication failed");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Validate access token with proper expiry checks
|
|
538
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
539
|
+
*/
|
|
540
|
+
validateAccessToken(token) {
|
|
541
|
+
try {
|
|
542
|
+
const decoded = this.jwtService.verify(token);
|
|
543
|
+
this.logger.debug(`Access token decoded for user: ${decoded.userId}`);
|
|
544
|
+
if (decoded.exp) {
|
|
545
|
+
const expiryTime = decoded.exp * 1e3;
|
|
546
|
+
const currentTime = Date.now();
|
|
547
|
+
const timeRemaining = expiryTime - currentTime;
|
|
548
|
+
this.logger.debug(`Access token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
549
|
+
}
|
|
550
|
+
return decoded;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (error instanceof import_common4.UnauthorizedException) {
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
555
|
+
const jwtError = error;
|
|
556
|
+
if (jwtError?.name === "TokenExpiredError") {
|
|
557
|
+
this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);
|
|
558
|
+
throw new import_common4.UnauthorizedException("Access token has expired");
|
|
559
|
+
}
|
|
560
|
+
if (jwtError?.name === "JsonWebTokenError") {
|
|
561
|
+
this.logger.warn(`Access token verification failed: ${jwtError?.message}`);
|
|
562
|
+
throw new import_common4.UnauthorizedException("Invalid access token");
|
|
563
|
+
}
|
|
564
|
+
if (jwtError?.name === "NotBeforeError") {
|
|
565
|
+
this.logger.warn("Access token used before valid (nbf claim)");
|
|
566
|
+
throw new import_common4.UnauthorizedException("Access token not yet valid");
|
|
567
|
+
}
|
|
568
|
+
this.logger.error("Unexpected error validating access token", error);
|
|
569
|
+
throw new import_common4.UnauthorizedException("Access token validation failed");
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Validate refresh token with proper expiry checks
|
|
574
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
575
|
+
*/
|
|
576
|
+
validateRefreshToken(token) {
|
|
577
|
+
const jwtSecret = this.configService.get("JWT_REFRESH_SECRET") || this.configService.get("JWT_SECRET");
|
|
578
|
+
this.validateRefreshTokenWithSecret(token, jwtSecret);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Helper to validate refresh token with specific secret
|
|
582
|
+
*/
|
|
583
|
+
validateRefreshTokenWithSecret(token, secret) {
|
|
584
|
+
if (!secret) {
|
|
585
|
+
this.logger.error("JWT secret not configured for refresh token validation");
|
|
586
|
+
throw new import_common4.UnauthorizedException("Server configuration error");
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
const decoded = jwt.verify(token, secret, {
|
|
590
|
+
algorithms: [
|
|
591
|
+
"HS256",
|
|
592
|
+
"HS512",
|
|
593
|
+
"RS256"
|
|
594
|
+
]
|
|
595
|
+
});
|
|
596
|
+
this.logger.debug(`Refresh token decoded for user: ${decoded.userId}`);
|
|
597
|
+
if (decoded.exp) {
|
|
598
|
+
const expiryTime = decoded.exp * 1e3;
|
|
599
|
+
const currentTime = Date.now();
|
|
600
|
+
if (currentTime > expiryTime) {
|
|
601
|
+
this.logger.warn("Refresh token has expired");
|
|
602
|
+
throw new import_common4.UnauthorizedException("Refresh token has expired. Please login again");
|
|
603
|
+
}
|
|
604
|
+
const timeRemaining = expiryTime - currentTime;
|
|
605
|
+
this.logger.debug(`Refresh token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
606
|
+
}
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (error instanceof import_common4.UnauthorizedException) {
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
const jwtError = error;
|
|
612
|
+
if (jwtError?.name === "TokenExpiredError") {
|
|
613
|
+
this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);
|
|
614
|
+
throw new import_common4.UnauthorizedException("Refresh token has expired. Please login again");
|
|
615
|
+
}
|
|
616
|
+
if (jwtError?.name === "JsonWebTokenError") {
|
|
617
|
+
this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);
|
|
618
|
+
throw new import_common4.UnauthorizedException("Invalid refresh token");
|
|
619
|
+
}
|
|
620
|
+
if (jwtError?.name === "NotBeforeError") {
|
|
621
|
+
this.logger.warn("Refresh token used before valid (nbf claim)");
|
|
622
|
+
throw new import_common4.UnauthorizedException("Refresh token not yet valid");
|
|
623
|
+
}
|
|
624
|
+
this.logger.error("Unexpected error validating refresh token", error);
|
|
625
|
+
throw new import_common4.UnauthorizedException("Refresh token validation failed");
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
VrittiAuthGuard = _ts_decorate4([
|
|
630
|
+
(0, import_common4.Injectable)({
|
|
631
|
+
scope: import_common4.Scope.REQUEST
|
|
632
|
+
}),
|
|
633
|
+
_ts_metadata3("design:type", Function),
|
|
634
|
+
_ts_metadata3("design:paramtypes", [
|
|
635
|
+
typeof import_core2.Reflector === "undefined" ? Object : import_core2.Reflector,
|
|
636
|
+
typeof import_config.ConfigService === "undefined" ? Object : import_config.ConfigService,
|
|
637
|
+
typeof import_jwt.JwtService === "undefined" ? Object : import_jwt.JwtService,
|
|
638
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
639
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
640
|
+
])
|
|
641
|
+
], VrittiAuthGuard);
|
|
642
|
+
|
|
643
|
+
// src/auth/auth-config.module.ts
|
|
644
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
645
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
646
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
647
|
+
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;
|
|
648
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
649
|
+
}
|
|
650
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
651
|
+
var AuthConfigModule = class _AuthConfigModule {
|
|
652
|
+
static {
|
|
653
|
+
__name(this, "AuthConfigModule");
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Register the auth module with async configuration
|
|
657
|
+
*
|
|
658
|
+
* This method:
|
|
659
|
+
* 1. Configures JwtModule with JWT_SECRET from ConfigService
|
|
660
|
+
* 2. Provides VrittiAuthGuard globally (applies to all routes)
|
|
661
|
+
* 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
|
|
662
|
+
*
|
|
663
|
+
* @returns Dynamic module configuration
|
|
664
|
+
*/
|
|
665
|
+
static forRootAsync() {
|
|
666
|
+
return {
|
|
667
|
+
module: _AuthConfigModule,
|
|
668
|
+
imports: [
|
|
669
|
+
import_config2.ConfigModule,
|
|
670
|
+
RequestModule,
|
|
671
|
+
import_jwt2.JwtModule.registerAsync({
|
|
672
|
+
imports: [
|
|
673
|
+
import_config2.ConfigModule
|
|
674
|
+
],
|
|
675
|
+
inject: [
|
|
676
|
+
import_config2.ConfigService
|
|
677
|
+
],
|
|
678
|
+
useFactory: /* @__PURE__ */ __name((config) => ({
|
|
679
|
+
secret: config.get("JWT_SECRET"),
|
|
680
|
+
signOptions: {
|
|
681
|
+
algorithm: "HS256"
|
|
682
|
+
}
|
|
683
|
+
}), "useFactory")
|
|
684
|
+
})
|
|
685
|
+
],
|
|
686
|
+
providers: [
|
|
687
|
+
{
|
|
688
|
+
provide: import_core3.APP_GUARD,
|
|
689
|
+
useClass: VrittiAuthGuard
|
|
690
|
+
}
|
|
691
|
+
],
|
|
692
|
+
exports: [
|
|
693
|
+
import_jwt2.JwtModule
|
|
694
|
+
]
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
AuthConfigModule = _ts_decorate5([
|
|
699
|
+
(0, import_common5.Global)(),
|
|
700
|
+
(0, import_common5.Module)({})
|
|
701
|
+
], AuthConfigModule);
|
|
702
|
+
|
|
703
|
+
// src/database/database.module.ts
|
|
704
|
+
var import_common10 = require("@nestjs/common");
|
|
705
|
+
var import_core4 = require("@nestjs/core");
|
|
706
|
+
|
|
707
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
708
|
+
var import_common7 = require("@nestjs/common");
|
|
709
|
+
var import_operators = require("rxjs/operators");
|
|
710
|
+
|
|
264
711
|
// src/database/services/tenant-context.service.ts
|
|
265
|
-
var
|
|
266
|
-
function
|
|
712
|
+
var import_common6 = require("@nestjs/common");
|
|
713
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
267
714
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
268
715
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
269
716
|
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;
|
|
270
717
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
271
718
|
}
|
|
272
|
-
__name(
|
|
719
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
273
720
|
var TenantContextService = class {
|
|
274
721
|
static {
|
|
275
722
|
__name(this, "TenantContextService");
|
|
@@ -300,7 +747,7 @@ var TenantContextService = class {
|
|
|
300
747
|
*/
|
|
301
748
|
getTenant() {
|
|
302
749
|
if (!this.tenantInfo) {
|
|
303
|
-
throw new
|
|
750
|
+
throw new import_common6.UnauthorizedException("Tenant context not set");
|
|
304
751
|
}
|
|
305
752
|
return this.tenantInfo;
|
|
306
753
|
}
|
|
@@ -341,67 +788,221 @@ var TenantContextService = class {
|
|
|
341
788
|
return this.tenantInfo?.subdomain ?? null;
|
|
342
789
|
}
|
|
343
790
|
};
|
|
344
|
-
TenantContextService =
|
|
345
|
-
(0,
|
|
346
|
-
scope:
|
|
791
|
+
TenantContextService = _ts_decorate6([
|
|
792
|
+
(0, import_common6.Injectable)({
|
|
793
|
+
scope: import_common6.Scope.REQUEST
|
|
347
794
|
})
|
|
348
795
|
], TenantContextService);
|
|
349
796
|
|
|
350
|
-
// src/database/
|
|
351
|
-
|
|
352
|
-
function _ts_decorate3(decorators, target, key, desc) {
|
|
797
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
798
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
353
799
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
354
800
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
355
801
|
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;
|
|
356
802
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
357
803
|
}
|
|
358
|
-
__name(
|
|
359
|
-
function
|
|
804
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
805
|
+
function _ts_metadata4(k, v) {
|
|
360
806
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
361
807
|
}
|
|
362
|
-
__name(
|
|
363
|
-
|
|
364
|
-
return function(target, key) {
|
|
365
|
-
decorator(target, key, paramIndex);
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
__name(_ts_param2, "_ts_param");
|
|
369
|
-
var TenantDatabaseService = class _TenantDatabaseService {
|
|
808
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
809
|
+
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
370
810
|
static {
|
|
371
|
-
__name(this, "
|
|
811
|
+
__name(this, "MessageTenantContextInterceptor");
|
|
372
812
|
}
|
|
373
|
-
options;
|
|
374
813
|
tenantContext;
|
|
375
|
-
logger = new
|
|
376
|
-
|
|
377
|
-
clients = /* @__PURE__ */ new Map();
|
|
378
|
-
/** Track last usage time for idle connection cleanup */
|
|
379
|
-
clientLastUsed = /* @__PURE__ */ new Map();
|
|
380
|
-
/** Cleanup interval timer */
|
|
381
|
-
cleanupInterval;
|
|
382
|
-
constructor(options, tenantContext) {
|
|
383
|
-
this.options = options;
|
|
814
|
+
logger = new import_common7.Logger(_MessageTenantContextInterceptor.name);
|
|
815
|
+
constructor(tenantContext) {
|
|
384
816
|
this.tenantContext = tenantContext;
|
|
385
|
-
this.startConnectionCleaner();
|
|
386
817
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
818
|
+
intercept(context, next) {
|
|
819
|
+
const contextType = context.getType();
|
|
820
|
+
if (contextType === "rpc") {
|
|
821
|
+
const rpcContext = context.switchToRpc();
|
|
822
|
+
const payload = rpcContext.getData();
|
|
823
|
+
if (payload && payload.tenant) {
|
|
824
|
+
const tenant = payload.tenant;
|
|
825
|
+
this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
|
|
826
|
+
try {
|
|
827
|
+
this.tenantContext.setTenant(tenant);
|
|
828
|
+
this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
this.logger.error("Failed to set tenant context from message", error);
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
this.logger.warn("Message payload missing tenant information");
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return next.handle().pipe((0, import_operators.tap)({
|
|
837
|
+
next: /* @__PURE__ */ __name(() => {
|
|
838
|
+
this.cleanupContext();
|
|
839
|
+
}, "next"),
|
|
840
|
+
error: /* @__PURE__ */ __name(() => {
|
|
841
|
+
this.cleanupContext();
|
|
842
|
+
}, "error"),
|
|
843
|
+
complete: /* @__PURE__ */ __name(() => {
|
|
844
|
+
this.cleanupContext();
|
|
845
|
+
}, "complete")
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Clean up tenant context after message is processed
|
|
850
|
+
*/
|
|
851
|
+
cleanupContext() {
|
|
852
|
+
if (this.tenantContext.hasTenant()) {
|
|
853
|
+
const tenant = this.tenantContext.getTenantIdSafe();
|
|
854
|
+
this.tenantContext.clearTenant();
|
|
855
|
+
this.logger.debug(`Cleaned up tenant context: ${tenant}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
MessageTenantContextInterceptor = _ts_decorate7([
|
|
860
|
+
(0, import_common7.Injectable)({
|
|
861
|
+
scope: import_common7.Scope.REQUEST
|
|
862
|
+
}),
|
|
863
|
+
_ts_metadata4("design:type", Function),
|
|
864
|
+
_ts_metadata4("design:paramtypes", [
|
|
865
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
866
|
+
])
|
|
867
|
+
], MessageTenantContextInterceptor);
|
|
868
|
+
|
|
869
|
+
// src/database/interceptors/tenant-context.interceptor.ts
|
|
870
|
+
var import_common8 = require("@nestjs/common");
|
|
871
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
872
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
873
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
874
|
+
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;
|
|
875
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
876
|
+
}
|
|
877
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
878
|
+
function _ts_metadata5(k, v) {
|
|
879
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
880
|
+
}
|
|
881
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
882
|
+
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
883
|
+
static {
|
|
884
|
+
__name(this, "TenantContextInterceptor");
|
|
885
|
+
}
|
|
886
|
+
tenantContext;
|
|
887
|
+
primaryDatabase;
|
|
888
|
+
requestService;
|
|
889
|
+
logger = new import_common8.Logger(_TenantContextInterceptor.name);
|
|
890
|
+
constructor(tenantContext, primaryDatabase, requestService) {
|
|
891
|
+
this.tenantContext = tenantContext;
|
|
892
|
+
this.primaryDatabase = primaryDatabase;
|
|
893
|
+
this.requestService = requestService;
|
|
894
|
+
}
|
|
895
|
+
async intercept(context, next) {
|
|
896
|
+
const request = context.switchToHttp().getRequest();
|
|
897
|
+
this.logger.debug(`Processing request: ${request.method} ${request.url}`);
|
|
898
|
+
try {
|
|
899
|
+
const tenantIdentifier = this.requestService.getTenantIdentifier();
|
|
900
|
+
if (!tenantIdentifier) {
|
|
901
|
+
throw new import_common8.UnauthorizedException("Tenant identifier not found in request");
|
|
902
|
+
}
|
|
903
|
+
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
904
|
+
if (tenantIdentifier === "cloud") {
|
|
905
|
+
this.logger.log("Cloud platform access detected, skipping tenant context setup");
|
|
906
|
+
return next.handle();
|
|
907
|
+
}
|
|
908
|
+
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
909
|
+
if (!tenantInfo) {
|
|
910
|
+
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
911
|
+
throw new import_common8.UnauthorizedException("Invalid tenant");
|
|
912
|
+
}
|
|
913
|
+
if (tenantInfo.status !== "ACTIVE") {
|
|
914
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
915
|
+
throw new import_common8.UnauthorizedException(`Tenant is ${tenantInfo.status}`);
|
|
916
|
+
}
|
|
917
|
+
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
918
|
+
this.tenantContext.setTenant(tenantInfo);
|
|
919
|
+
request.tenant = tenantInfo;
|
|
920
|
+
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
this.logger.error("Failed to set tenant context", error);
|
|
923
|
+
throw error;
|
|
924
|
+
}
|
|
925
|
+
return next.handle();
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
TenantContextInterceptor = _ts_decorate8([
|
|
929
|
+
(0, import_common8.Injectable)({
|
|
930
|
+
scope: import_common8.Scope.REQUEST
|
|
931
|
+
}),
|
|
932
|
+
_ts_metadata5("design:type", Function),
|
|
933
|
+
_ts_metadata5("design:paramtypes", [
|
|
934
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
935
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
936
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
937
|
+
])
|
|
938
|
+
], TenantContextInterceptor);
|
|
939
|
+
|
|
940
|
+
// src/database/services/tenant-database.service.ts
|
|
941
|
+
var import_common9 = require("@nestjs/common");
|
|
942
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
943
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
944
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
945
|
+
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;
|
|
946
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
947
|
+
}
|
|
948
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
949
|
+
function _ts_metadata6(k, v) {
|
|
950
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
951
|
+
}
|
|
952
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
953
|
+
function _ts_param3(paramIndex, decorator) {
|
|
954
|
+
return function(target, key) {
|
|
955
|
+
decorator(target, key, paramIndex);
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
__name(_ts_param3, "_ts_param");
|
|
959
|
+
var TenantDatabaseService = class _TenantDatabaseService {
|
|
960
|
+
static {
|
|
961
|
+
__name(this, "TenantDatabaseService");
|
|
962
|
+
}
|
|
963
|
+
options;
|
|
964
|
+
tenantContext;
|
|
965
|
+
logger = new import_common9.Logger(_TenantDatabaseService.name);
|
|
966
|
+
/** Connection pool: Map<cacheKey, DbClient> */
|
|
967
|
+
clients = /* @__PURE__ */ new Map();
|
|
968
|
+
/** Track last usage time for idle connection cleanup */
|
|
969
|
+
clientLastUsed = /* @__PURE__ */ new Map();
|
|
970
|
+
/** Cleanup interval timer */
|
|
971
|
+
cleanupInterval;
|
|
972
|
+
constructor(options, tenantContext) {
|
|
973
|
+
this.options = options;
|
|
974
|
+
this.tenantContext = tenantContext;
|
|
975
|
+
this.startConnectionCleaner();
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Get the Prisma client for the current tenant's database.
|
|
979
|
+
* This returns the tenant-scoped database client.
|
|
980
|
+
*
|
|
981
|
+
* @returns Tenant-scoped database client instance
|
|
982
|
+
* @throws UnauthorizedException if tenant context not set
|
|
983
|
+
* @throws InternalServerErrorException if connection fails
|
|
984
|
+
*/
|
|
985
|
+
get prismaClient() {
|
|
986
|
+
return this.getDbClient();
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Get tenant-scoped database client for the current request/message
|
|
990
|
+
*
|
|
991
|
+
* This method:
|
|
992
|
+
* 1. Gets tenant info from TenantContextService
|
|
993
|
+
* 2. Builds a connection URL based on tenant type
|
|
994
|
+
* 3. Returns cached client if exists, otherwise creates new one
|
|
995
|
+
*
|
|
996
|
+
* @returns Promise<Database client instance>
|
|
997
|
+
* @throws UnauthorizedException if tenant context not set
|
|
998
|
+
* @throws InternalServerErrorException if connection fails
|
|
999
|
+
*
|
|
1000
|
+
* @example
|
|
1001
|
+
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
1002
|
+
* const users = await dbClient.user.findMany();
|
|
1003
|
+
*/
|
|
1004
|
+
async getDbClient() {
|
|
1005
|
+
const tenant = this.tenantContext.getTenant();
|
|
405
1006
|
const cacheKey = this.buildCacheKey(tenant);
|
|
406
1007
|
if (this.clients.has(cacheKey)) {
|
|
407
1008
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
@@ -437,7 +1038,7 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
437
1038
|
return client;
|
|
438
1039
|
} catch (error) {
|
|
439
1040
|
this.logger.error(`Failed to create database connection for tenant: ${tenant.subdomain}`, error);
|
|
440
|
-
throw new
|
|
1041
|
+
throw new import_common9.InternalServerErrorException("Failed to connect to tenant database");
|
|
441
1042
|
}
|
|
442
1043
|
}
|
|
443
1044
|
/**
|
|
@@ -528,87 +1129,116 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
528
1129
|
this.logger.log("All database connections closed");
|
|
529
1130
|
}
|
|
530
1131
|
};
|
|
531
|
-
TenantDatabaseService =
|
|
532
|
-
(0,
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1132
|
+
TenantDatabaseService = _ts_decorate9([
|
|
1133
|
+
(0, import_common9.Injectable)(),
|
|
1134
|
+
_ts_param3(0, (0, import_common9.Inject)(DATABASE_MODULE_OPTIONS)),
|
|
1135
|
+
_ts_metadata6("design:type", Function),
|
|
1136
|
+
_ts_metadata6("design:paramtypes", [
|
|
536
1137
|
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions,
|
|
537
1138
|
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
538
1139
|
])
|
|
539
1140
|
], TenantDatabaseService);
|
|
540
1141
|
|
|
541
1142
|
// src/database/database.module.ts
|
|
542
|
-
function
|
|
1143
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
543
1144
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
544
1145
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
545
1146
|
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;
|
|
546
1147
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
547
1148
|
}
|
|
548
|
-
__name(
|
|
1149
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
549
1150
|
var DatabaseModule = class _DatabaseModule {
|
|
550
1151
|
static {
|
|
551
1152
|
__name(this, "DatabaseModule");
|
|
552
1153
|
}
|
|
553
1154
|
/**
|
|
554
|
-
*
|
|
1155
|
+
* Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
|
|
555
1156
|
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
1157
|
+
* This mode is for API Gateways that handle HTTP requests:
|
|
1158
|
+
* - Automatically registers TenantContextInterceptor
|
|
1159
|
+
* - Extracts tenant from subdomain or x-tenant-id header
|
|
1160
|
+
* - Queries primary database for tenant configuration
|
|
1161
|
+
* - Provides PrimaryDatabaseService for tenant lookup
|
|
1162
|
+
*
|
|
1163
|
+
* @param options Async configuration options
|
|
1164
|
+
* @returns Dynamic module configuration with HTTP interceptor
|
|
1165
|
+
*
|
|
1166
|
+
* @example
|
|
1167
|
+
* DatabaseModule.forServer({
|
|
1168
|
+
* inject: [ConfigService],
|
|
1169
|
+
* useFactory: (config: ConfigService) => ({
|
|
1170
|
+
* primaryDb: {
|
|
1171
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
1172
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
1173
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
1174
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
1175
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
1176
|
+
* },
|
|
1177
|
+
* prismaClientConstructor: PrismaClient,
|
|
1178
|
+
* }),
|
|
1179
|
+
* })
|
|
558
1180
|
*/
|
|
559
|
-
static
|
|
560
|
-
|
|
561
|
-
{
|
|
562
|
-
provide: DATABASE_MODULE_OPTIONS,
|
|
563
|
-
useValue: options
|
|
564
|
-
},
|
|
565
|
-
TenantDatabaseService,
|
|
566
|
-
TenantContextService,
|
|
567
|
-
PrimaryDatabaseService
|
|
568
|
-
];
|
|
569
|
-
return {
|
|
570
|
-
module: _DatabaseModule,
|
|
571
|
-
providers,
|
|
572
|
-
exports: [
|
|
573
|
-
TenantDatabaseService,
|
|
574
|
-
TenantContextService,
|
|
575
|
-
PrimaryDatabaseService
|
|
576
|
-
]
|
|
577
|
-
};
|
|
1181
|
+
static forServer(options) {
|
|
1182
|
+
return this.createDynamicModule(options, "server");
|
|
578
1183
|
}
|
|
579
1184
|
/**
|
|
580
|
-
*
|
|
1185
|
+
* Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
|
|
581
1186
|
*
|
|
582
|
-
*
|
|
1187
|
+
* This mode is for microservices that process messages from queues:
|
|
1188
|
+
* - Automatically registers MessageTenantContextInterceptor
|
|
1189
|
+
* - Extracts tenant from RabbitMQ message patterns
|
|
1190
|
+
* - No primary database needed (tenant comes from message context)
|
|
583
1191
|
*
|
|
584
1192
|
* @param options Async configuration options
|
|
585
|
-
* @returns Dynamic module configuration
|
|
1193
|
+
* @returns Dynamic module configuration with message interceptor
|
|
586
1194
|
*
|
|
587
1195
|
* @example
|
|
588
|
-
* DatabaseModule.
|
|
589
|
-
*
|
|
590
|
-
* useFactory:
|
|
591
|
-
* cloudDatabaseUrl: config.get('CLOUD_DATABASE_URL'),
|
|
1196
|
+
* DatabaseModule.forMicroservice({
|
|
1197
|
+
* inject: [ConfigService],
|
|
1198
|
+
* useFactory: (config: ConfigService) => ({
|
|
592
1199
|
* prismaClientConstructor: PrismaClient,
|
|
593
|
-
* tenantResolver: 'subdomain',
|
|
594
1200
|
* }),
|
|
595
|
-
* inject: [ConfigService],
|
|
596
1201
|
* })
|
|
597
1202
|
*/
|
|
598
|
-
static
|
|
1203
|
+
static forMicroservice(options) {
|
|
1204
|
+
return this.createDynamicModule(options, "microservice");
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Internal helper to create dynamic module with conditional interceptor registration
|
|
1208
|
+
*
|
|
1209
|
+
* @param options Configuration options
|
|
1210
|
+
* @param mode Mode of operation (gateway or microservice)
|
|
1211
|
+
* @returns Dynamic module configuration
|
|
1212
|
+
*/
|
|
1213
|
+
static createDynamicModule(options, mode) {
|
|
599
1214
|
const asyncProvider = {
|
|
600
1215
|
provide: DATABASE_MODULE_OPTIONS,
|
|
601
1216
|
useFactory: options.useFactory,
|
|
602
1217
|
inject: options.inject || []
|
|
603
1218
|
};
|
|
1219
|
+
const providers = [
|
|
1220
|
+
asyncProvider,
|
|
1221
|
+
TenantContextService,
|
|
1222
|
+
PrimaryDatabaseService,
|
|
1223
|
+
TenantDatabaseService
|
|
1224
|
+
];
|
|
1225
|
+
if (mode === "server") {
|
|
1226
|
+
providers.push({
|
|
1227
|
+
provide: import_core4.APP_INTERCEPTOR,
|
|
1228
|
+
useClass: TenantContextInterceptor
|
|
1229
|
+
});
|
|
1230
|
+
} else {
|
|
1231
|
+
providers.push({
|
|
1232
|
+
provide: import_core4.APP_INTERCEPTOR,
|
|
1233
|
+
useClass: MessageTenantContextInterceptor
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
604
1236
|
return {
|
|
605
1237
|
module: _DatabaseModule,
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
TenantContextService,
|
|
609
|
-
PrimaryDatabaseService,
|
|
610
|
-
TenantDatabaseService
|
|
1238
|
+
imports: [
|
|
1239
|
+
RequestModule
|
|
611
1240
|
],
|
|
1241
|
+
providers,
|
|
612
1242
|
exports: [
|
|
613
1243
|
TenantDatabaseService,
|
|
614
1244
|
TenantContextService,
|
|
@@ -618,236 +1248,797 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
618
1248
|
};
|
|
619
1249
|
}
|
|
620
1250
|
};
|
|
621
|
-
DatabaseModule =
|
|
622
|
-
(0,
|
|
623
|
-
(0,
|
|
1251
|
+
DatabaseModule = _ts_decorate10([
|
|
1252
|
+
(0, import_common10.Global)(),
|
|
1253
|
+
(0, import_common10.Module)({})
|
|
624
1254
|
], DatabaseModule);
|
|
625
1255
|
|
|
626
|
-
// src/database/
|
|
627
|
-
var
|
|
628
|
-
var
|
|
629
|
-
function _ts_decorate5(decorators, target, key, desc) {
|
|
630
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
631
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
632
|
-
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;
|
|
633
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
634
|
-
}
|
|
635
|
-
__name(_ts_decorate5, "_ts_decorate");
|
|
636
|
-
function _ts_metadata3(k, v) {
|
|
637
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
638
|
-
}
|
|
639
|
-
__name(_ts_metadata3, "_ts_metadata");
|
|
640
|
-
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
1256
|
+
// src/database/repositories/primary-base.repository.ts
|
|
1257
|
+
var import_common11 = require("@nestjs/common");
|
|
1258
|
+
var PrimaryBaseRepository = class {
|
|
641
1259
|
static {
|
|
642
|
-
__name(this, "
|
|
643
|
-
}
|
|
644
|
-
tenantContext;
|
|
645
|
-
logger = new import_common5.Logger(_MessageTenantContextInterceptor.name);
|
|
646
|
-
constructor(tenantContext) {
|
|
647
|
-
this.tenantContext = tenantContext;
|
|
1260
|
+
__name(this, "PrimaryBaseRepository");
|
|
648
1261
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
|
|
660
|
-
} catch (error) {
|
|
661
|
-
this.logger.error("Failed to set tenant context from message", error);
|
|
662
|
-
}
|
|
663
|
-
} else {
|
|
664
|
-
this.logger.warn("Message payload missing tenant information");
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
return next.handle().pipe((0, import_operators.tap)({
|
|
668
|
-
next: /* @__PURE__ */ __name(() => {
|
|
669
|
-
this.cleanupContext();
|
|
670
|
-
}, "next"),
|
|
671
|
-
error: /* @__PURE__ */ __name(() => {
|
|
672
|
-
this.cleanupContext();
|
|
673
|
-
}, "error"),
|
|
674
|
-
complete: /* @__PURE__ */ __name(() => {
|
|
675
|
-
this.cleanupContext();
|
|
676
|
-
}, "complete")
|
|
677
|
-
}));
|
|
1262
|
+
database;
|
|
1263
|
+
logger;
|
|
1264
|
+
modelGetter;
|
|
1265
|
+
/**
|
|
1266
|
+
* Lazy getter for Prisma client.
|
|
1267
|
+
* Accesses the client from the database service only when needed,
|
|
1268
|
+
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1269
|
+
*/
|
|
1270
|
+
get prisma() {
|
|
1271
|
+
return this.database.prismaClient;
|
|
678
1272
|
}
|
|
679
1273
|
/**
|
|
680
|
-
*
|
|
1274
|
+
* Lazy getter for the Prisma model delegate.
|
|
1275
|
+
* Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.
|
|
681
1276
|
*/
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1277
|
+
get model() {
|
|
1278
|
+
return this.modelGetter(this.prisma);
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Create a new repository instance
|
|
1282
|
+
*
|
|
1283
|
+
* @param database - The primary database service
|
|
1284
|
+
* @param getModel - Function that returns the Prisma model delegate from the client
|
|
1285
|
+
*
|
|
1286
|
+
* @example
|
|
1287
|
+
* ```typescript
|
|
1288
|
+
* // Standard usage with full parameter name
|
|
1289
|
+
* constructor(database: PrimaryDatabaseService) {
|
|
1290
|
+
* super(database, (prisma) => prisma.user);
|
|
1291
|
+
* }
|
|
1292
|
+
*
|
|
1293
|
+
* // Short syntax
|
|
1294
|
+
* constructor(database: PrimaryDatabaseService) {
|
|
1295
|
+
* super(database, (p) => p.user);
|
|
1296
|
+
* }
|
|
1297
|
+
*
|
|
1298
|
+
* // Complex model names
|
|
1299
|
+
* constructor(database: PrimaryDatabaseService) {
|
|
1300
|
+
* super(database, (p) => p.emailVerification);
|
|
1301
|
+
* }
|
|
1302
|
+
* ```
|
|
1303
|
+
*/
|
|
1304
|
+
constructor(database, getModel) {
|
|
1305
|
+
this.database = database;
|
|
1306
|
+
this.logger = new import_common11.Logger(this.constructor.name);
|
|
1307
|
+
this.modelGetter = getModel;
|
|
1308
|
+
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Create a new record
|
|
1312
|
+
*
|
|
1313
|
+
* @param data - The data to create the record with
|
|
1314
|
+
* @returns Promise resolving to the created record
|
|
1315
|
+
*
|
|
1316
|
+
* @example
|
|
1317
|
+
* ```typescript
|
|
1318
|
+
* const user = await userRepository.create({
|
|
1319
|
+
* email: 'user@example.com',
|
|
1320
|
+
* name: 'John Doe'
|
|
1321
|
+
* });
|
|
1322
|
+
* ```
|
|
1323
|
+
*/
|
|
1324
|
+
async create(data) {
|
|
1325
|
+
this.logger.log("Creating record");
|
|
1326
|
+
return await this.model.create({
|
|
1327
|
+
data
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Find a single record by ID
|
|
1332
|
+
*
|
|
1333
|
+
* @param id - The record ID
|
|
1334
|
+
* @returns Promise resolving to the record or null if not found
|
|
1335
|
+
*
|
|
1336
|
+
* @example
|
|
1337
|
+
* ```typescript
|
|
1338
|
+
* const user = await userRepository.findById('user-id-123');
|
|
1339
|
+
* ```
|
|
1340
|
+
*/
|
|
1341
|
+
async findById(id) {
|
|
1342
|
+
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1343
|
+
return await this.model.findUnique({
|
|
1344
|
+
where: {
|
|
1345
|
+
id
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Find a single record with custom where clause
|
|
1351
|
+
*
|
|
1352
|
+
* @param where - The where clause or findUnique args
|
|
1353
|
+
* @returns Promise resolving to the record or null if not found
|
|
1354
|
+
*
|
|
1355
|
+
* @example
|
|
1356
|
+
* ```typescript
|
|
1357
|
+
* // Simple where clause
|
|
1358
|
+
* const user = await userRepository.findOne({ email: 'user@example.com' });
|
|
1359
|
+
*
|
|
1360
|
+
* // With include
|
|
1361
|
+
* const user = await userRepository.findOne({
|
|
1362
|
+
* where: { email: 'user@example.com' },
|
|
1363
|
+
* include: { posts: true }
|
|
1364
|
+
* });
|
|
1365
|
+
* ```
|
|
1366
|
+
*/
|
|
1367
|
+
async findOne(where) {
|
|
1368
|
+
this.logger.debug("Finding record with custom query");
|
|
1369
|
+
return await this.model.findUnique(typeof where === "object" && "where" in where ? where : {
|
|
1370
|
+
where
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Find multiple records
|
|
1375
|
+
*
|
|
1376
|
+
* @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
|
|
1377
|
+
* @returns Promise resolving to an array of records
|
|
1378
|
+
*
|
|
1379
|
+
* @example
|
|
1380
|
+
* ```typescript
|
|
1381
|
+
* // Find all users
|
|
1382
|
+
* const users = await userRepository.findMany();
|
|
1383
|
+
*
|
|
1384
|
+
* // Find with filtering and pagination
|
|
1385
|
+
* const users = await userRepository.findMany({
|
|
1386
|
+
* where: { status: 'ACTIVE' },
|
|
1387
|
+
* orderBy: { createdAt: 'desc' },
|
|
1388
|
+
* take: 10,
|
|
1389
|
+
* skip: 0
|
|
1390
|
+
* });
|
|
1391
|
+
* ```
|
|
1392
|
+
*/
|
|
1393
|
+
async findMany(args) {
|
|
1394
|
+
this.logger.debug("Finding multiple records");
|
|
1395
|
+
return await this.model.findMany(args);
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Update a record by ID
|
|
1399
|
+
*
|
|
1400
|
+
* @param id - The record ID
|
|
1401
|
+
* @param data - The data to update
|
|
1402
|
+
* @returns Promise resolving to the updated record
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* ```typescript
|
|
1406
|
+
* const user = await userRepository.update('user-id-123', {
|
|
1407
|
+
* name: 'Jane Doe'
|
|
1408
|
+
* });
|
|
1409
|
+
* ```
|
|
1410
|
+
*/
|
|
1411
|
+
async update(id, data) {
|
|
1412
|
+
this.logger.log(`Updating record with ID: ${id}`);
|
|
1413
|
+
return await this.model.update({
|
|
1414
|
+
where: {
|
|
1415
|
+
id
|
|
1416
|
+
},
|
|
1417
|
+
data
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Update multiple records
|
|
1422
|
+
*
|
|
1423
|
+
* @param where - The where clause to match records
|
|
1424
|
+
* @param data - The data to update
|
|
1425
|
+
* @returns Promise resolving to the count of updated records
|
|
1426
|
+
*
|
|
1427
|
+
* @example
|
|
1428
|
+
* ```typescript
|
|
1429
|
+
* const result = await userRepository.updateMany(
|
|
1430
|
+
* { status: 'PENDING' },
|
|
1431
|
+
* { status: 'ACTIVE' }
|
|
1432
|
+
* );
|
|
1433
|
+
* console.log(`Updated ${result.count} users`);
|
|
1434
|
+
* ```
|
|
1435
|
+
*/
|
|
1436
|
+
async updateMany(where, data) {
|
|
1437
|
+
this.logger.log("Updating multiple records");
|
|
1438
|
+
return await this.model.updateMany({
|
|
1439
|
+
where,
|
|
1440
|
+
data
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Delete a record by ID
|
|
1445
|
+
*
|
|
1446
|
+
* @param id - The record ID
|
|
1447
|
+
* @returns Promise resolving to the deleted record
|
|
1448
|
+
*
|
|
1449
|
+
* @example
|
|
1450
|
+
* ```typescript
|
|
1451
|
+
* const user = await userRepository.delete('user-id-123');
|
|
1452
|
+
* ```
|
|
1453
|
+
*/
|
|
1454
|
+
async delete(id) {
|
|
1455
|
+
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1456
|
+
return await this.model.delete({
|
|
1457
|
+
where: {
|
|
1458
|
+
id
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Delete multiple records
|
|
1464
|
+
*
|
|
1465
|
+
* @param where - The where clause to match records
|
|
1466
|
+
* @returns Promise resolving to the count of deleted records
|
|
1467
|
+
*
|
|
1468
|
+
* @example
|
|
1469
|
+
* ```typescript
|
|
1470
|
+
* const result = await userRepository.deleteMany({
|
|
1471
|
+
* status: 'INACTIVE',
|
|
1472
|
+
* createdAt: { lt: new Date('2020-01-01') }
|
|
1473
|
+
* });
|
|
1474
|
+
* console.log(`Deleted ${result.count} users`);
|
|
1475
|
+
* ```
|
|
1476
|
+
*/
|
|
1477
|
+
async deleteMany(where) {
|
|
1478
|
+
this.logger.log("Deleting multiple records");
|
|
1479
|
+
return await this.model.deleteMany({
|
|
1480
|
+
where
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Count records
|
|
1485
|
+
*
|
|
1486
|
+
* @param where - Optional where clause to filter records
|
|
1487
|
+
* @returns Promise resolving to the count of records
|
|
1488
|
+
*
|
|
1489
|
+
* @example
|
|
1490
|
+
* ```typescript
|
|
1491
|
+
* // Count all users
|
|
1492
|
+
* const total = await userRepository.count();
|
|
1493
|
+
*
|
|
1494
|
+
* // Count active users
|
|
1495
|
+
* const activeCount = await userRepository.count({ status: 'ACTIVE' });
|
|
1496
|
+
* ```
|
|
1497
|
+
*/
|
|
1498
|
+
async count(where) {
|
|
1499
|
+
this.logger.debug("Counting records");
|
|
1500
|
+
return await this.model.count({
|
|
1501
|
+
where
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Check if a record exists
|
|
1506
|
+
*
|
|
1507
|
+
* @param where - The where clause to match records
|
|
1508
|
+
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1509
|
+
*
|
|
1510
|
+
* @example
|
|
1511
|
+
* ```typescript
|
|
1512
|
+
* const emailExists = await userRepository.exists({
|
|
1513
|
+
* email: 'user@example.com'
|
|
1514
|
+
* });
|
|
1515
|
+
* ```
|
|
1516
|
+
*/
|
|
1517
|
+
async exists(where) {
|
|
1518
|
+
const count = await this.model.count({
|
|
1519
|
+
where
|
|
1520
|
+
});
|
|
1521
|
+
return count > 0;
|
|
688
1522
|
}
|
|
689
1523
|
};
|
|
690
|
-
MessageTenantContextInterceptor = _ts_decorate5([
|
|
691
|
-
(0, import_common5.Injectable)({
|
|
692
|
-
scope: import_common5.Scope.REQUEST
|
|
693
|
-
}),
|
|
694
|
-
_ts_metadata3("design:type", Function),
|
|
695
|
-
_ts_metadata3("design:paramtypes", [
|
|
696
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
697
|
-
])
|
|
698
|
-
], MessageTenantContextInterceptor);
|
|
699
1524
|
|
|
700
|
-
// src/database/
|
|
701
|
-
var
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (!host) {
|
|
706
|
-
return null;
|
|
1525
|
+
// src/database/repositories/tenant-base.repository.ts
|
|
1526
|
+
var import_common12 = require("@nestjs/common");
|
|
1527
|
+
var TenantBaseRepository = class {
|
|
1528
|
+
static {
|
|
1529
|
+
__name(this, "TenantBaseRepository");
|
|
707
1530
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1531
|
+
database;
|
|
1532
|
+
logger;
|
|
1533
|
+
modelGetter;
|
|
1534
|
+
/**
|
|
1535
|
+
* Lazy getter for Prisma client.
|
|
1536
|
+
* Accesses the client from the database service only when needed,
|
|
1537
|
+
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1538
|
+
*/
|
|
1539
|
+
get prisma() {
|
|
1540
|
+
return this.database.prismaClient;
|
|
711
1541
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1542
|
+
/**
|
|
1543
|
+
* Lazy getter for the Prisma model delegate.
|
|
1544
|
+
* Returns the specific model (e.g., prisma.product, prisma.order) for this repository.
|
|
1545
|
+
*/
|
|
1546
|
+
get model() {
|
|
1547
|
+
return this.modelGetter(this.prisma);
|
|
715
1548
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1549
|
+
/**
|
|
1550
|
+
* Create a new repository instance
|
|
1551
|
+
*
|
|
1552
|
+
* @param database - The tenant database service
|
|
1553
|
+
* @param getModel - Function that returns the Prisma model delegate from the client
|
|
1554
|
+
*
|
|
1555
|
+
* @example
|
|
1556
|
+
* ```typescript
|
|
1557
|
+
* // Standard usage with full parameter name
|
|
1558
|
+
* constructor(database: TenantDatabaseService) {
|
|
1559
|
+
* super(database, (prisma) => prisma.product);
|
|
1560
|
+
* }
|
|
1561
|
+
*
|
|
1562
|
+
* // Short syntax
|
|
1563
|
+
* constructor(database: TenantDatabaseService) {
|
|
1564
|
+
* super(database, (p) => p.product);
|
|
1565
|
+
* }
|
|
1566
|
+
*
|
|
1567
|
+
* // Complex model names
|
|
1568
|
+
* constructor(database: TenantDatabaseService) {
|
|
1569
|
+
* super(database, (p) => p.inventoryItem);
|
|
1570
|
+
* }
|
|
1571
|
+
* ```
|
|
1572
|
+
*/
|
|
1573
|
+
constructor(database, getModel) {
|
|
1574
|
+
this.database = database;
|
|
1575
|
+
this.logger = new import_common12.Logger(this.constructor.name);
|
|
1576
|
+
this.modelGetter = getModel;
|
|
1577
|
+
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Create a new record
|
|
1581
|
+
*
|
|
1582
|
+
* @param data - The data to create the record with
|
|
1583
|
+
* @returns Promise resolving to the created record
|
|
1584
|
+
*
|
|
1585
|
+
* @example
|
|
1586
|
+
* ```typescript
|
|
1587
|
+
* const product = await productRepository.create({
|
|
1588
|
+
* name: 'Widget',
|
|
1589
|
+
* sku: 'WDG-001',
|
|
1590
|
+
* price: 9.99
|
|
1591
|
+
* });
|
|
1592
|
+
* ```
|
|
1593
|
+
*/
|
|
1594
|
+
async create(data) {
|
|
1595
|
+
this.logger.log("Creating record");
|
|
1596
|
+
return await this.model.create({
|
|
1597
|
+
data
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Find a single record by ID
|
|
1602
|
+
*
|
|
1603
|
+
* @param id - The record ID
|
|
1604
|
+
* @returns Promise resolving to the record or null if not found
|
|
1605
|
+
*
|
|
1606
|
+
* @example
|
|
1607
|
+
* ```typescript
|
|
1608
|
+
* const product = await productRepository.findById('product-id-123');
|
|
1609
|
+
* ```
|
|
1610
|
+
*/
|
|
1611
|
+
async findById(id) {
|
|
1612
|
+
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1613
|
+
return await this.model.findUnique({
|
|
1614
|
+
where: {
|
|
1615
|
+
id
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Find a single record with custom where clause
|
|
1621
|
+
*
|
|
1622
|
+
* @param where - The where clause or findUnique args
|
|
1623
|
+
* @returns Promise resolving to the record or null if not found
|
|
1624
|
+
*
|
|
1625
|
+
* @example
|
|
1626
|
+
* ```typescript
|
|
1627
|
+
* // Simple where clause
|
|
1628
|
+
* const product = await productRepository.findOne({ sku: 'WDG-001' });
|
|
1629
|
+
*
|
|
1630
|
+
* // With include
|
|
1631
|
+
* const product = await productRepository.findOne({
|
|
1632
|
+
* where: { sku: 'WDG-001' },
|
|
1633
|
+
* include: { category: true }
|
|
1634
|
+
* });
|
|
1635
|
+
* ```
|
|
1636
|
+
*/
|
|
1637
|
+
async findOne(where) {
|
|
1638
|
+
this.logger.debug("Finding record with custom query");
|
|
1639
|
+
return await this.model.findUnique(typeof where === "object" && "where" in where ? where : {
|
|
1640
|
+
where
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Find multiple records
|
|
1645
|
+
*
|
|
1646
|
+
* @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
|
|
1647
|
+
* @returns Promise resolving to an array of records
|
|
1648
|
+
*
|
|
1649
|
+
* @example
|
|
1650
|
+
* ```typescript
|
|
1651
|
+
* // Find all products
|
|
1652
|
+
* const products = await productRepository.findMany();
|
|
1653
|
+
*
|
|
1654
|
+
* // Find with filtering and pagination
|
|
1655
|
+
* const products = await productRepository.findMany({
|
|
1656
|
+
* where: { status: 'ACTIVE' },
|
|
1657
|
+
* orderBy: { createdAt: 'desc' },
|
|
1658
|
+
* take: 10,
|
|
1659
|
+
* skip: 0
|
|
1660
|
+
* });
|
|
1661
|
+
* ```
|
|
1662
|
+
*/
|
|
1663
|
+
async findMany(args) {
|
|
1664
|
+
this.logger.debug("Finding multiple records");
|
|
1665
|
+
return await this.model.findMany(args);
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Update a record by ID
|
|
1669
|
+
*
|
|
1670
|
+
* @param id - The record ID
|
|
1671
|
+
* @param data - The data to update
|
|
1672
|
+
* @returns Promise resolving to the updated record
|
|
1673
|
+
*
|
|
1674
|
+
* @example
|
|
1675
|
+
* ```typescript
|
|
1676
|
+
* const product = await productRepository.update('product-id-123', {
|
|
1677
|
+
* price: 12.99
|
|
1678
|
+
* });
|
|
1679
|
+
* ```
|
|
1680
|
+
*/
|
|
1681
|
+
async update(id, data) {
|
|
1682
|
+
this.logger.log(`Updating record with ID: ${id}`);
|
|
1683
|
+
return await this.model.update({
|
|
1684
|
+
where: {
|
|
1685
|
+
id
|
|
1686
|
+
},
|
|
1687
|
+
data
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Update multiple records
|
|
1692
|
+
*
|
|
1693
|
+
* @param where - The where clause to match records
|
|
1694
|
+
* @param data - The data to update
|
|
1695
|
+
* @returns Promise resolving to the count of updated records
|
|
1696
|
+
*
|
|
1697
|
+
* @example
|
|
1698
|
+
* ```typescript
|
|
1699
|
+
* const result = await productRepository.updateMany(
|
|
1700
|
+
* { status: 'PENDING' },
|
|
1701
|
+
* { status: 'ACTIVE' }
|
|
1702
|
+
* );
|
|
1703
|
+
* console.log(`Updated ${result.count} products`);
|
|
1704
|
+
* ```
|
|
1705
|
+
*/
|
|
1706
|
+
async updateMany(where, data) {
|
|
1707
|
+
this.logger.log("Updating multiple records");
|
|
1708
|
+
return await this.model.updateMany({
|
|
1709
|
+
where,
|
|
1710
|
+
data
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Delete a record by ID
|
|
1715
|
+
*
|
|
1716
|
+
* @param id - The record ID
|
|
1717
|
+
* @returns Promise resolving to the deleted record
|
|
1718
|
+
*
|
|
1719
|
+
* @example
|
|
1720
|
+
* ```typescript
|
|
1721
|
+
* const product = await productRepository.delete('product-id-123');
|
|
1722
|
+
* ```
|
|
1723
|
+
*/
|
|
1724
|
+
async delete(id) {
|
|
1725
|
+
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1726
|
+
return await this.model.delete({
|
|
1727
|
+
where: {
|
|
1728
|
+
id
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Delete multiple records
|
|
1734
|
+
*
|
|
1735
|
+
* @param where - The where clause to match records
|
|
1736
|
+
* @returns Promise resolving to the count of deleted records
|
|
1737
|
+
*
|
|
1738
|
+
* @example
|
|
1739
|
+
* ```typescript
|
|
1740
|
+
* const result = await productRepository.deleteMany({
|
|
1741
|
+
* status: 'INACTIVE',
|
|
1742
|
+
* createdAt: { lt: new Date('2020-01-01') }
|
|
1743
|
+
* });
|
|
1744
|
+
* console.log(`Deleted ${result.count} products`);
|
|
1745
|
+
* ```
|
|
1746
|
+
*/
|
|
1747
|
+
async deleteMany(where) {
|
|
1748
|
+
this.logger.log("Deleting multiple records");
|
|
1749
|
+
return await this.model.deleteMany({
|
|
1750
|
+
where
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Count records
|
|
1755
|
+
*
|
|
1756
|
+
* @param where - Optional where clause to filter records
|
|
1757
|
+
* @returns Promise resolving to the count of records
|
|
1758
|
+
*
|
|
1759
|
+
* @example
|
|
1760
|
+
* ```typescript
|
|
1761
|
+
* // Count all products
|
|
1762
|
+
* const total = await productRepository.count();
|
|
1763
|
+
*
|
|
1764
|
+
* // Count active products
|
|
1765
|
+
* const activeCount = await productRepository.count({ status: 'ACTIVE' });
|
|
1766
|
+
* ```
|
|
1767
|
+
*/
|
|
1768
|
+
async count(where) {
|
|
1769
|
+
this.logger.debug("Counting records");
|
|
1770
|
+
return await this.model.count({
|
|
1771
|
+
where
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Check if a record exists
|
|
1776
|
+
*
|
|
1777
|
+
* @param where - The where clause to match records
|
|
1778
|
+
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```typescript
|
|
1782
|
+
* const skuExists = await productRepository.exists({
|
|
1783
|
+
* sku: 'WDG-001'
|
|
1784
|
+
* });
|
|
1785
|
+
* ```
|
|
1786
|
+
*/
|
|
1787
|
+
async exists(where) {
|
|
1788
|
+
const count = await this.model.count({
|
|
1789
|
+
where
|
|
1790
|
+
});
|
|
1791
|
+
return count > 0;
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
719
1794
|
|
|
720
|
-
// src/database/
|
|
721
|
-
|
|
1795
|
+
// src/database/decorators/tenant.decorator.ts
|
|
1796
|
+
var import_common13 = require("@nestjs/common");
|
|
1797
|
+
var Tenant = (0, import_common13.createParamDecorator)((data, ctx) => {
|
|
1798
|
+
const request = ctx.switchToHttp().getRequest();
|
|
1799
|
+
const tenantContext = request.app?.get?.(TenantContextService);
|
|
1800
|
+
if (!tenantContext) {
|
|
1801
|
+
throw new Error("TenantContextService not found.");
|
|
1802
|
+
}
|
|
1803
|
+
return tenantContext.getTenant();
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
// src/auth/decorators/onboarding.decorator.ts
|
|
1807
|
+
var import_common14 = require("@nestjs/common");
|
|
1808
|
+
var Onboarding = /* @__PURE__ */ __name(() => (0, import_common14.SetMetadata)("isOnboarding", true), "Onboarding");
|
|
1809
|
+
|
|
1810
|
+
// src/auth/decorators/public.decorator.ts
|
|
1811
|
+
var import_common15 = require("@nestjs/common");
|
|
1812
|
+
var Public = /* @__PURE__ */ __name(() => (0, import_common15.SetMetadata)("isPublic", true), "Public");
|
|
1813
|
+
|
|
1814
|
+
// src/http/http.module.ts
|
|
1815
|
+
var import_common17 = require("@nestjs/common");
|
|
1816
|
+
|
|
1817
|
+
// src/http/guards/csrf.guard.ts
|
|
1818
|
+
var import_common16 = require("@nestjs/common");
|
|
1819
|
+
var import_core5 = require("@nestjs/core");
|
|
1820
|
+
function _ts_decorate11(decorators, target, key, desc) {
|
|
722
1821
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
723
1822
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
724
1823
|
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;
|
|
725
1824
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
726
1825
|
}
|
|
727
|
-
__name(
|
|
728
|
-
function
|
|
1826
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
1827
|
+
function _ts_metadata7(k, v) {
|
|
729
1828
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
730
1829
|
}
|
|
731
|
-
__name(
|
|
732
|
-
|
|
733
|
-
return function(target, key) {
|
|
734
|
-
decorator(target, key, paramIndex);
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
__name(_ts_param3, "_ts_param");
|
|
738
|
-
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
1830
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
1831
|
+
var CsrfGuard = class _CsrfGuard {
|
|
739
1832
|
static {
|
|
740
|
-
__name(this, "
|
|
1833
|
+
__name(this, "CsrfGuard");
|
|
741
1834
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
constructor(tenantContext, primaryDatabase, options) {
|
|
747
|
-
this.tenantContext = tenantContext;
|
|
748
|
-
this.primaryDatabase = primaryDatabase;
|
|
749
|
-
this.options = options;
|
|
1835
|
+
reflector;
|
|
1836
|
+
logger = new import_common16.Logger(_CsrfGuard.name);
|
|
1837
|
+
constructor(reflector) {
|
|
1838
|
+
this.reflector = reflector;
|
|
750
1839
|
}
|
|
751
|
-
async
|
|
1840
|
+
async canActivate(context) {
|
|
752
1841
|
const request = context.switchToHttp().getRequest();
|
|
753
|
-
|
|
1842
|
+
const reply = context.switchToHttp().getResponse();
|
|
1843
|
+
const safeMethods = [
|
|
1844
|
+
"GET",
|
|
1845
|
+
"HEAD",
|
|
1846
|
+
"OPTIONS"
|
|
1847
|
+
];
|
|
1848
|
+
if (safeMethods.includes(request.method)) {
|
|
1849
|
+
return true;
|
|
1850
|
+
}
|
|
754
1851
|
try {
|
|
755
|
-
const
|
|
756
|
-
if (!
|
|
757
|
-
|
|
1852
|
+
const fastifyInstance = request.server;
|
|
1853
|
+
if (!fastifyInstance.csrfProtection) {
|
|
1854
|
+
this.logger.error("CSRF protection plugin not found. Ensure @fastify/csrf-protection is registered.");
|
|
1855
|
+
throw new import_common16.ForbiddenException("CSRF protection not configured");
|
|
758
1856
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
771
|
-
throw new import_common6.UnauthorizedException(`Tenant is ${tenantInfo.status}`);
|
|
772
|
-
}
|
|
773
|
-
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
774
|
-
this.tenantContext.setTenant(tenantInfo);
|
|
775
|
-
request.tenant = tenantInfo;
|
|
776
|
-
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
1857
|
+
await new Promise((resolve, reject) => {
|
|
1858
|
+
fastifyInstance.csrfProtection(request, reply, (err) => {
|
|
1859
|
+
if (err) {
|
|
1860
|
+
reject(err);
|
|
1861
|
+
} else {
|
|
1862
|
+
resolve();
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
});
|
|
1866
|
+
this.logger.debug(`CSRF validation successful for ${request.method} ${request.url}`);
|
|
1867
|
+
return true;
|
|
777
1868
|
} catch (error) {
|
|
778
|
-
this.logger.
|
|
779
|
-
throw
|
|
1869
|
+
this.logger.warn(`CSRF validation failed for ${request.method} ${request.url}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1870
|
+
throw new import_common16.ForbiddenException({
|
|
1871
|
+
errors: [
|
|
1872
|
+
{
|
|
1873
|
+
field: "csrf",
|
|
1874
|
+
message: "Invalid or missing CSRF token"
|
|
1875
|
+
}
|
|
1876
|
+
],
|
|
1877
|
+
message: "CSRF validation failed"
|
|
1878
|
+
});
|
|
780
1879
|
}
|
|
781
|
-
return next.handle();
|
|
782
1880
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1881
|
+
};
|
|
1882
|
+
CsrfGuard = _ts_decorate11([
|
|
1883
|
+
(0, import_common16.Injectable)(),
|
|
1884
|
+
_ts_metadata7("design:type", Function),
|
|
1885
|
+
_ts_metadata7("design:paramtypes", [
|
|
1886
|
+
typeof import_core5.Reflector === "undefined" ? Object : import_core5.Reflector
|
|
1887
|
+
])
|
|
1888
|
+
], CsrfGuard);
|
|
1889
|
+
|
|
1890
|
+
// src/http/http.module.ts
|
|
1891
|
+
function _ts_decorate12(decorators, target, key, desc) {
|
|
1892
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1893
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1894
|
+
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;
|
|
1895
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1896
|
+
}
|
|
1897
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
1898
|
+
var HttpModule = class {
|
|
1899
|
+
static {
|
|
1900
|
+
__name(this, "HttpModule");
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
HttpModule = _ts_decorate12([
|
|
1904
|
+
(0, import_common17.Module)({
|
|
1905
|
+
providers: [
|
|
1906
|
+
CsrfGuard
|
|
1907
|
+
],
|
|
1908
|
+
exports: [
|
|
1909
|
+
CsrfGuard
|
|
1910
|
+
]
|
|
1911
|
+
})
|
|
1912
|
+
], HttpModule);
|
|
1913
|
+
|
|
1914
|
+
// src/http/filters/http-exception.filter.ts
|
|
1915
|
+
var import_common18 = require("@nestjs/common");
|
|
1916
|
+
function _ts_decorate13(decorators, target, key, desc) {
|
|
1917
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1918
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1919
|
+
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;
|
|
1920
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1921
|
+
}
|
|
1922
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
1923
|
+
var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
1924
|
+
static {
|
|
1925
|
+
__name(this, "HttpExceptionFilter");
|
|
1926
|
+
}
|
|
1927
|
+
logger = new import_common18.Logger(_HttpExceptionFilter.name);
|
|
1928
|
+
catch(exception, host) {
|
|
1929
|
+
const ctx = host.switchToHttp();
|
|
1930
|
+
const reply = ctx.getResponse();
|
|
1931
|
+
const request = ctx.getRequest();
|
|
1932
|
+
let status = import_common18.HttpStatus.INTERNAL_SERVER_ERROR;
|
|
1933
|
+
let errors = [];
|
|
1934
|
+
let message;
|
|
1935
|
+
if (exception instanceof import_common18.HttpException) {
|
|
1936
|
+
status = exception.getStatus();
|
|
1937
|
+
const exceptionResponse = exception.getResponse();
|
|
1938
|
+
if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
|
|
1939
|
+
const responseObj = exceptionResponse;
|
|
1940
|
+
if (Array.isArray(responseObj.message)) {
|
|
1941
|
+
errors = this.parseValidationErrors(responseObj.message);
|
|
1942
|
+
message = "Validation failed";
|
|
1943
|
+
} else if (responseObj.message) {
|
|
1944
|
+
errors = [
|
|
1945
|
+
{
|
|
1946
|
+
field: "general",
|
|
1947
|
+
message: responseObj.message
|
|
1948
|
+
}
|
|
1949
|
+
];
|
|
1950
|
+
message = responseObj.message;
|
|
1951
|
+
}
|
|
1952
|
+
} else if (typeof exceptionResponse === "string") {
|
|
1953
|
+
errors = [
|
|
1954
|
+
{
|
|
1955
|
+
field: "general",
|
|
1956
|
+
message: exceptionResponse
|
|
1957
|
+
}
|
|
1958
|
+
];
|
|
1959
|
+
message = exceptionResponse;
|
|
792
1960
|
}
|
|
1961
|
+
} else if (exception instanceof Error) {
|
|
1962
|
+
this.logger.error(`Unhandled error: ${exception.message}`, exception.stack);
|
|
1963
|
+
errors = [
|
|
1964
|
+
{
|
|
1965
|
+
field: "general",
|
|
1966
|
+
message: "Internal server error"
|
|
1967
|
+
}
|
|
1968
|
+
];
|
|
1969
|
+
message = "An unexpected error occurred";
|
|
1970
|
+
} else {
|
|
1971
|
+
this.logger.error("Unknown exception type", exception);
|
|
1972
|
+
errors = [
|
|
1973
|
+
{
|
|
1974
|
+
field: "general",
|
|
1975
|
+
message: "Internal server error"
|
|
1976
|
+
}
|
|
1977
|
+
];
|
|
1978
|
+
message = "An unexpected error occurred";
|
|
793
1979
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (
|
|
802
|
-
|
|
1980
|
+
const errorResponse = {
|
|
1981
|
+
errors,
|
|
1982
|
+
message,
|
|
1983
|
+
statusCode: status,
|
|
1984
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1985
|
+
path: request.url
|
|
1986
|
+
};
|
|
1987
|
+
if (status >= 500) {
|
|
1988
|
+
this.logger.error(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`, exception instanceof Error ? exception.stack : void 0);
|
|
1989
|
+
} else {
|
|
1990
|
+
this.logger.warn(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`);
|
|
803
1991
|
}
|
|
804
|
-
|
|
805
|
-
return extractSubdomain(host);
|
|
1992
|
+
reply.status(status).send(errorResponse);
|
|
806
1993
|
}
|
|
807
1994
|
/**
|
|
808
|
-
*
|
|
809
|
-
* Checks x-tenant-id and x-subdomain headers
|
|
1995
|
+
* Parse class-validator error messages into field-specific errors
|
|
810
1996
|
*/
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1997
|
+
parseValidationErrors(messages) {
|
|
1998
|
+
const errors = [];
|
|
1999
|
+
for (const msg of messages) {
|
|
2000
|
+
if (typeof msg === "string") {
|
|
2001
|
+
errors.push({
|
|
2002
|
+
field: "general",
|
|
2003
|
+
message: msg
|
|
2004
|
+
});
|
|
2005
|
+
} else if (typeof msg === "object" && msg.property && msg.constraints) {
|
|
2006
|
+
const field = msg.property;
|
|
2007
|
+
const constraintMessages = Object.values(msg.constraints);
|
|
2008
|
+
for (const constraintMsg of constraintMessages) {
|
|
2009
|
+
errors.push({
|
|
2010
|
+
field,
|
|
2011
|
+
message: constraintMsg
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
return errors.length > 0 ? errors : [
|
|
2017
|
+
{
|
|
2018
|
+
field: "general",
|
|
2019
|
+
message: "Validation failed"
|
|
2020
|
+
}
|
|
2021
|
+
];
|
|
817
2022
|
}
|
|
818
2023
|
};
|
|
819
|
-
|
|
820
|
-
(0,
|
|
821
|
-
|
|
822
|
-
}),
|
|
823
|
-
_ts_param3(2, (0, import_common6.Inject)(DATABASE_MODULE_OPTIONS)),
|
|
824
|
-
_ts_metadata4("design:type", Function),
|
|
825
|
-
_ts_metadata4("design:paramtypes", [
|
|
826
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
827
|
-
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
828
|
-
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
829
|
-
])
|
|
830
|
-
], TenantContextInterceptor);
|
|
831
|
-
|
|
832
|
-
// src/database/decorators/tenant.decorator.ts
|
|
833
|
-
var import_common7 = require("@nestjs/common");
|
|
834
|
-
var Tenant = (0, import_common7.createParamDecorator)((data, ctx) => {
|
|
835
|
-
const request = ctx.switchToHttp().getRequest();
|
|
836
|
-
const tenantContext = request.app?.get?.(TenantContextService);
|
|
837
|
-
if (!tenantContext) {
|
|
838
|
-
throw new Error("TenantContextService not found.");
|
|
839
|
-
}
|
|
840
|
-
return tenantContext.getTenant();
|
|
841
|
-
});
|
|
2024
|
+
HttpExceptionFilter = _ts_decorate13([
|
|
2025
|
+
(0, import_common18.Catch)()
|
|
2026
|
+
], HttpExceptionFilter);
|
|
842
2027
|
// Annotate the CommonJS export names for ESM import in node:
|
|
843
2028
|
0 && (module.exports = {
|
|
2029
|
+
AuthConfigModule,
|
|
2030
|
+
CsrfGuard,
|
|
844
2031
|
DatabaseModule,
|
|
845
|
-
|
|
2032
|
+
HttpExceptionFilter,
|
|
2033
|
+
HttpModule,
|
|
2034
|
+
Onboarding,
|
|
2035
|
+
PrimaryBaseRepository,
|
|
846
2036
|
PrimaryDatabaseService,
|
|
2037
|
+
Public,
|
|
847
2038
|
Tenant,
|
|
848
|
-
|
|
2039
|
+
TenantBaseRepository,
|
|
849
2040
|
TenantContextService,
|
|
850
2041
|
TenantDatabaseService,
|
|
851
|
-
|
|
2042
|
+
VrittiAuthGuard
|
|
852
2043
|
});
|
|
853
2044
|
//# sourceMappingURL=index.cjs.map
|