@flusys/nestjs-shared 9.1.1 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +119 -18
  2. package/classes/api-controller.class.d.ts +1 -0
  3. package/classes/api-service.class.d.ts +7 -2
  4. package/classes/hybrid-cache.class.d.ts +17 -4
  5. package/constants/index.d.ts +1 -0
  6. package/constants/permissions.d.ts +9 -0
  7. package/fesm/183.js +7 -7
  8. package/fesm/47.js +1520 -0
  9. package/fesm/470.js +333 -382
  10. package/fesm/667.js +17 -17
  11. package/fesm/681.js +326 -129
  12. package/fesm/{948.js → 997.js} +45 -23
  13. package/fesm/chunks/123.js +17 -9
  14. package/fesm/chunks/37.js +5 -5
  15. package/fesm/classes/index.js +4 -3
  16. package/fesm/constants/index.js +18 -7
  17. package/fesm/decorators/index.js +120 -39
  18. package/fesm/dtos/index.js +1 -1
  19. package/fesm/entities/index.js +8 -8
  20. package/fesm/enums/index.js +2 -2
  21. package/fesm/exceptions/index.js +12 -12
  22. package/fesm/guards/index.js +201 -68
  23. package/fesm/index.js +95 -42
  24. package/fesm/interceptors/index.js +290 -590
  25. package/fesm/interfaces/index.js +97 -8
  26. package/fesm/middlewares/index.js +4 -4
  27. package/fesm/modules/index.js +2 -2
  28. package/fesm/pipes/index.js +8 -8
  29. package/fesm/utils/index.js +150 -478
  30. package/interceptors/idempotency.interceptor.d.ts +14 -2
  31. package/interceptors/slug.interceptor.d.ts +1 -0
  32. package/interfaces/expression.interface.d.ts +45 -0
  33. package/interfaces/index.d.ts +1 -0
  34. package/interfaces/permission-action-registry.interface.d.ts +1 -0
  35. package/interfaces/permission.interface.d.ts +1 -0
  36. package/modules/shared-permission-cache/shared-permission-cache.service.d.ts +3 -1
  37. package/modules/utils/utils.service.d.ts +6 -6
  38. package/package.json +3 -2
  39. package/utils/date-time.util.d.ts +0 -1
  40. package/utils/event-pattern.util.d.ts +1 -0
  41. package/utils/expression.util.d.ts +35 -0
  42. package/utils/index.d.ts +2 -0
  43. package/utils/permission-cache-key.util.d.ts +3 -0
  44. package/utils/query-helpers.util.d.ts +2 -2
  45. package/utils/safe-regex.util.d.ts +10 -0
  46. package/fesm/270.js +0 -659
package/fesm/270.js DELETED
@@ -1,659 +0,0 @@
1
- export const __rspack_esm_id = 270;
2
- export const __rspack_esm_ids = [270];
3
- export const __webpack_modules__ = {
4
- 301(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5
-
6
- // EXPORTS
7
- __webpack_require__.d(__webpack_exports__, {
8
- s8: () => (/* reexport */ permission_cache_key_util/* .DEFAULT_COMPANY_PERMISSION_KEY_FORMAT */.s8),
9
- q6: () => (/* reexport */ permission_cache_key_util/* .DEFAULT_USER_PERMISSION_KEY_FORMAT */.q6),
10
- Df: () => (/* reexport */ (/* inlined export .MAX_PERMISSION_LOGIC_CODES */50)),
11
- kW: () => (/* reexport */ (/* inlined export .MAX_PERMISSION_LOGIC_DEPTH */5)),
12
- f7: () => (/* reexport */ applyBranchFilter),
13
- wf: () => (/* reexport */ applyCompanyFilter),
14
- kQ: () => (/* reexport */ buildCompanyWhereCondition),
15
- fJ: () => (/* reexport */ buildCookieOptions),
16
- oV: () => (/* reexport */ buildFrontendUrl),
17
- nt: () => (/* reexport */ permission_cache_key_util/* .buildPermissionCacheKey */.nt),
18
- qW: () => (/* reexport */ cache_tenant_util/* .cacheTenantId */.q),
19
- Sf: () => (/* reexport */ collectBranchAncestorIds),
20
- qY: () => (/* reexport */ collectBranchDescendantIds),
21
- ZD: () => (/* reexport */ escapeHtml),
22
- qo: () => (/* reexport */ escapeHtmlVariables),
23
- Ng: () => (/* reexport */ permission_match_util/* .evaluatePermissionLogic */.Ng),
24
- hX: () => (/* reexport */ extractFrontendUrl),
25
- Sd: () => (/* reexport */ formatInTimezone),
26
- sN: () => (/* reexport */ generateSecureToken),
27
- z9: () => (/* reexport */ generateSlug),
28
- oo: () => (/* reexport */ generateUniqueSlug),
29
- gN: () => (/* reexport */ getBranchPath),
30
- oB: () => (/* reexport */ getTodayInTimezone),
31
- f: () => (/* reexport */ hasCompanyId),
32
- QK: () => (/* reexport */ hashToken),
33
- QU: () => (/* reexport */ isBranchWithin),
34
- wh: () => (/* reexport */ isBrowserRequest),
35
- _d: () => (/* reexport */ isExpired),
36
- _c: () => (/* reexport */ event_pattern_util/* .isMultiWildcard */._c),
37
- lk: () => (/* reexport */ optional_module_util/* .loadOptionalModule */.l),
38
- CQ: () => (/* reexport */ permission_match_util/* .loadPermissionCodes */.CQ),
39
- vf: () => (/* reexport */ localToUtc),
40
- qe: () => (/* reexport */ event_pattern_util/* .matchEventPattern */.qe),
41
- xY: () => (/* reexport */ permission_match_util/* .matchesPermission */.xY),
42
- RG: () => (/* reexport */ messageRef),
43
- Ty: () => (/* reexport */ normalizeBranchIds),
44
- Ju: () => (/* reexport */ parseDurationToMs),
45
- MO: () => (/* reexport */ permission_match_util/* .permissionLogicCodes */.MO),
46
- HU: () => (/* reexport */ permission_match_util/* .permissionLogicProblem */.HU),
47
- IN: () => (/* reexport */ event_pattern_util/* .sanitizeEventPayload */.IN),
48
- v4: () => (/* reexport */ utcToLocal),
49
- bE: () => (/* reexport */ validateCompanyOwnership),
50
- o0: () => (/* reexport */ withSavepoint)
51
- });
52
-
53
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/branch-hierarchy.util.ts
54
- /** Each id once, in input order, without empty values. */ function normalizeBranchIds(ids) {
55
- const list = Array.isArray(ids) ? ids : [
56
- ids
57
- ];
58
- return [
59
- ...new Set(list.filter((id)=>typeof id === 'string' && id !== ''))
60
- ];
61
- }
62
- function depthLimit(maxDepth) {
63
- return typeof maxDepth === 'number' && Number.isFinite(maxDepth) && maxDepth >= 0 ? Math.floor(maxDepth) : Infinity;
64
- }
65
- /** The first node per id wins, so duplicates never change the answer. */ function indexNodes(nodes) {
66
- const byId = new Map();
67
- for (const node of nodes)if (node?.id && !byId.has(node.id)) byId.set(node.id, node);
68
- return byId;
69
- }
70
- function childrenIndex(nodes) {
71
- const children = new Map();
72
- for (const node of nodes){
73
- if (!node.parentId || node.parentId === node.id) continue;
74
- const siblings = children.get(node.parentId);
75
- if (siblings) siblings.push(node.id);
76
- else children.set(node.parentId, [
77
- node.id
78
- ]);
79
- }
80
- return children;
81
- }
82
- /**
83
- * Every branch under `rootIds`, level by level (children, then grandchildren, ...), from a flat
84
- * `{ id, parentId }` list. With `includeSelf` the given ids come first - kept even when they are not in
85
- * `nodes`, so a caller never loses a branch it asked about. Safe against duplicates and parent cycles.
86
- */ function collectBranchDescendantIds(nodes, rootIds, { includeSelf = false, maxDepth } = {}) {
87
- const roots = normalizeBranchIds(rootIds);
88
- const limit = depthLimit(maxDepth);
89
- const children = childrenIndex(indexNodes(nodes).values());
90
- const visited = new Set(roots);
91
- const descendants = [];
92
- let level = roots;
93
- for(let depth = 1; depth <= limit && level.length; depth++){
94
- const next = [];
95
- for (const parentId of level){
96
- for (const id of children.get(parentId) ?? []){
97
- if (visited.has(id)) continue;
98
- visited.add(id);
99
- descendants.push(id);
100
- next.push(id);
101
- }
102
- }
103
- level = next;
104
- }
105
- return includeSelf ? [
106
- ...roots,
107
- ...descendants
108
- ] : descendants;
109
- }
110
- /**
111
- * The parent chain of `branchId`, nearest parent first, up to the root. Only branches in `nodes` are
112
- * returned - the walk stops at a parent that is missing (e.g. soft-deleted). With `includeSelf` the
113
- * branch itself comes first even when it is not in `nodes`. Safe against parent cycles.
114
- */ function collectBranchAncestorIds(nodes, branchId, { includeSelf = false, maxDepth } = {}) {
115
- if (!branchId) return [];
116
- const byId = indexNodes(nodes);
117
- const limit = depthLimit(maxDepth);
118
- const ancestors = includeSelf ? [
119
- branchId
120
- ] : [];
121
- const visited = new Set([
122
- branchId
123
- ]);
124
- let parentId = byId.get(branchId)?.parentId;
125
- for(let depth = 1; depth <= limit && parentId && byId.has(parentId) && !visited.has(parentId); depth++){
126
- visited.add(parentId);
127
- ancestors.push(parentId);
128
- parentId = byId.get(parentId)?.parentId;
129
- }
130
- return ancestors;
131
- }
132
- /** True when `branchId` is `ancestorId` itself or sits anywhere under it. */ function isBranchWithin(nodes, branchId, ancestorId) {
133
- if (!branchId || !ancestorId) return false;
134
- return branchId === ancestorId || collectBranchAncestorIds(nodes, branchId).includes(ancestorId);
135
- }
136
- /**
137
- * The nodes from the top-most known ancestor down to `branchId` itself (e.g. for "HQ / East / East 1").
138
- * Empty when `branchId` is not in `nodes`.
139
- */ function getBranchPath(nodes, branchId) {
140
- if (!branchId) return [];
141
- const byId = indexNodes(nodes);
142
- if (!byId.has(branchId)) return [];
143
- return collectBranchAncestorIds(nodes, branchId, {
144
- includeSelf: true
145
- }).map((id)=>byId.get(id)).toReversed();
146
- }
147
-
148
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/cache-tenant.util.ts
149
- var cache_tenant_util = __webpack_require__(154);
150
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/date-time.util.ts
151
- function getOffsetMinutes(utcDate, timezone) {
152
- const extract = (tz)=>{
153
- const parts = new Intl.DateTimeFormat('en-US', {
154
- timeZone: tz,
155
- year: 'numeric',
156
- month: '2-digit',
157
- day: '2-digit',
158
- hour: '2-digit',
159
- minute: '2-digit',
160
- second: '2-digit',
161
- hour12: false
162
- }).formatToParts(utcDate);
163
- const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
164
- x.type,
165
- parseInt(x.value)
166
- ]));
167
- return Date.UTC(p['year'], p['month'] - 1, p['day'], p['hour'], p['minute'], p['second']);
168
- };
169
- return (extract(timezone) - extract('UTC')) / 60_000;
170
- }
171
- function localToUtc(dateStr, timeStr, timezone) {
172
- const [year, month, day] = dateStr.split('-').map(Number);
173
- const [hours, minutes] = timeStr.split(':').map(Number);
174
- const approx = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
175
- const offset = getOffsetMinutes(approx, timezone);
176
- const utcDate = new Date(approx.getTime() - offset * 60_000);
177
- // DST boundary guard: re-check offset after adjustment
178
- const finalOffset = getOffsetMinutes(utcDate, timezone);
179
- return finalOffset !== offset ? new Date(approx.getTime() - finalOffset * 60_000) : utcDate;
180
- }
181
- function utcToLocal(utcDate, timezone) {
182
- const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
183
- const parts = new Intl.DateTimeFormat('en-CA', {
184
- timeZone: timezone,
185
- year: 'numeric',
186
- month: '2-digit',
187
- day: '2-digit',
188
- hour: '2-digit',
189
- minute: '2-digit',
190
- hour12: false
191
- }).formatToParts(d);
192
- const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
193
- x.type,
194
- x.value
195
- ]));
196
- // en-CA formats midnight hour as '24' — normalize to '00'
197
- const hour = p['hour'] === '24' ? '00' : p['hour'];
198
- return {
199
- date: `${p['year']}-${p['month']}-${p['day']}`,
200
- time: `${hour}:${p['minute']}`,
201
- display: new Intl.DateTimeFormat('en-US', {
202
- timeZone: timezone,
203
- dateStyle: 'medium',
204
- timeStyle: 'short'
205
- }).format(d)
206
- };
207
- }
208
- function formatInTimezone(utcDate, timezone, options = {
209
- dateStyle: 'medium',
210
- timeStyle: 'short'
211
- }) {
212
- const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
213
- return new Intl.DateTimeFormat('en-US', {
214
- ...options,
215
- timeZone: timezone
216
- }).format(d);
217
- }
218
- function getTodayInTimezone(timezone) {
219
- return utcToLocal(new Date(), timezone).date;
220
- }
221
-
222
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/event-pattern.util.ts
223
- var event_pattern_util = __webpack_require__(456);
224
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/html-sanitizer.util.ts
225
- /**
226
- * HTML Sanitizer Utilities
227
- *
228
- * Provides functions for escaping HTML content to prevent XSS attacks.
229
- * Use these utilities when interpolating user-provided variables into HTML content.
230
- */ /**
231
- * HTML entity mapping for escaping special characters
232
- */ const HTML_ESCAPE_MAP = {
233
- '&': '&amp;',
234
- '<': '&lt;',
235
- '>': '&gt;',
236
- '"': '&quot;',
237
- "'": '&#x27;',
238
- '/': '&#x2F;',
239
- '`': '&#x60;',
240
- '=': '&#x3D;'
241
- };
242
- /**
243
- * Regex pattern matching characters that need HTML escaping
244
- */ const HTML_ESCAPE_REGEX = /[&<>"'`=/]/g;
245
- /**
246
- * Escapes HTML special characters to prevent XSS attacks.
247
- *
248
- * @param str - The string to escape
249
- * @returns The escaped string safe for HTML insertion
250
- *
251
- * @example
252
- * ```typescript
253
- * escapeHtml('<script>alert("xss")</script>')
254
- * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
255
- * ```
256
- */ function escapeHtml(str) {
257
- if (!str || typeof str !== 'string') {
258
- return str ?? '';
259
- }
260
- return str.replace(HTML_ESCAPE_REGEX, (char)=>HTML_ESCAPE_MAP[char]);
261
- }
262
- /**
263
- * Escapes all string values in a variables object for safe HTML interpolation.
264
- * Non-string values are converted to strings and escaped.
265
- *
266
- * @param variables - Object containing template variables
267
- * @returns New object with all string values HTML-escaped
268
- *
269
- * @example
270
- * ```typescript
271
- * escapeHtmlVariables({ name: '<script>xss</script>', count: 5 })
272
- * // Returns: { name: '&lt;script&gt;xss&lt;/script&gt;', count: '5' }
273
- * ```
274
- */ function escapeHtmlVariables(variables) {
275
- if (!variables || typeof variables !== 'object') {
276
- return {};
277
- }
278
- const escaped = {};
279
- for (const [key, value] of Object.entries(variables)){
280
- if (value === null || value === undefined) {
281
- escaped[key] = '';
282
- } else if (typeof value === 'string') {
283
- escaped[key] = escapeHtml(value);
284
- } else if (typeof value === 'object') {
285
- // For objects/arrays, stringify and escape
286
- escaped[key] = escapeHtml(JSON.stringify(value));
287
- } else {
288
- // For numbers, booleans, etc., convert to string (no escaping needed)
289
- escaped[key] = String(value);
290
- }
291
- }
292
- return escaped;
293
- }
294
-
295
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/message-ref.util.ts
296
- function messageRef(messageKey, messageVariables) {
297
- return messageVariables ? {
298
- messageKey,
299
- messageVariables
300
- } : {
301
- messageKey
302
- };
303
- }
304
-
305
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/optional-module.util.ts
306
- var optional_module_util = __webpack_require__(368);
307
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/permission-cache-key.util.ts
308
- var permission_cache_key_util = __webpack_require__(199);
309
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/permission-match.util.ts
310
- var permission_match_util = __webpack_require__(788);
311
- // EXTERNAL MODULE: external "@nestjs/common"
312
- var common_ = __webpack_require__(157);
313
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/constants/index.ts
314
- var constants = __webpack_require__(43);
315
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/query-helpers.util.ts
316
-
317
-
318
-
319
- /**
320
- * Apply company filter to a query builder if company feature is enabled.
321
- * Centralizes the common pattern of filtering by user's company.
322
- *
323
- * @param query - TypeORM SelectQueryBuilder
324
- * @param config - Company filter configuration
325
- * @param user - Current logged user (may be null)
326
- * @returns The modified query builder
327
- *
328
- * @example
329
- * ```typescript
330
- * applyCompanyFilter(query, {
331
- * isCompanyFeatureEnabled: this.config.isCompanyFeatureEnabled(),
332
- * entityAlias: 'file_manager',
333
- * }, user);
334
- * ```
335
- */ function applyCompanyFilter(query, config, user) {
336
- const columnName = config.columnName ?? 'companyId';
337
- if (config.isCompanyFeatureEnabled && user?.companyId) {
338
- query.andWhere(`${config.entityAlias}.${columnName} = :companyId`, {
339
- companyId: user.companyId
340
- });
341
- }
342
- return query;
343
- }
344
- /**
345
- * Restrict a query to rows of the given branches - typically a branch plus the granted branches under
346
- * it from `BRANCH_HIERARCHY_RESOLVER.getGrantedDescendantIds(userId, id, { includeSelf: true })`. Empty
347
- * ids are dropped; with none left only unassigned rows (if asked for) match, never everything.
348
- */ function applyBranchFilter(query, config, branchIds) {
349
- const ids = normalizeBranchIds(branchIds);
350
- const column = `${config.entityAlias}.${config.columnName ?? 'branchId'}`;
351
- if (!ids.length) return query.andWhere(config.includeUnassigned ? `${column} IS NULL` : '1 = 0');
352
- const inList = `${column} IN (:...branchFilterIds)`;
353
- return query.andWhere(config.includeUnassigned ? `(${inList} OR ${column} IS NULL)` : inList, {
354
- branchFilterIds: ids
355
- });
356
- }
357
- /**
358
- * Build a where condition object with optional company filter.
359
- *
360
- * @param baseWhere - Base where condition object
361
- * @param isCompanyFeatureEnabled - Whether company feature is enabled
362
- * @param user - Current logged user
363
- * @returns Where condition with companyId added if applicable
364
- *
365
- * @example
366
- * ```typescript
367
- * const where = buildCompanyWhereCondition(
368
- * { id: dto.id },
369
- * this.config.isCompanyFeatureEnabled(),
370
- * user
371
- * );
372
- * // Returns { id: dto.id, companyId: user.companyId } if company feature enabled
373
- * ```
374
- */ function buildCompanyWhereCondition(baseWhere, isCompanyFeatureEnabled, user) {
375
- if (isCompanyFeatureEnabled && user?.companyId) {
376
- return {
377
- ...baseWhere,
378
- companyId: user.companyId
379
- };
380
- }
381
- return baseWhere;
382
- }
383
- /**
384
- * Type guard to check if entity has companyId property
385
- */ function hasCompanyId(entity) {
386
- return entity !== null && typeof entity === 'object' && 'companyId' in entity;
387
- }
388
- /**
389
- * Validates that an entity with companyId belongs to the user's company.
390
- * Throws BadRequestException if the entity belongs to another company.
391
- *
392
- * @param entity - Entity to validate
393
- * @param user - Current logged user
394
- * @param isCompanyFeatureEnabled - Whether company feature is enabled
395
- * @param entityName - Name of entity for error message (e.g., 'Email configuration')
396
- * @throws BadRequestException if entity belongs to another company
397
- *
398
- * @example
399
- * ```typescript
400
- * validateCompanyOwnership(config, user, this.config.isCompanyFeatureEnabled(), 'Email configuration');
401
- * ```
402
- */ function validateCompanyOwnership(entity, user, isCompanyFeatureEnabled, entityName) {
403
- if (isCompanyFeatureEnabled && user?.companyId && hasCompanyId(entity)) {
404
- if (entity.companyId && entity.companyId !== user.companyId) {
405
- throw new common_.BadRequestException({
406
- message: `${entityName} belongs to another company`,
407
- messageKey: constants/* .AUTH_MESSAGES.ENTITY_BELOG_ANOTHER_COMPANY */.mK.ENTITY_BELOG_ANOTHER_COMPANY,
408
- messageVariables: {
409
- entity: entityName
410
- }
411
- });
412
- }
413
- }
414
- }
415
- /**
416
- * Runs `fn` inside a savepoint of an already-open transaction. A failure rolls back only what
417
- * `fn` did, so the caller can swallow the error and keep using the transaction - without it,
418
- * PostgreSQL aborts the whole transaction after any failed statement.
419
- */ async function withSavepoint(queryRunner, fn) {
420
- await queryRunner.startTransaction();
421
- try {
422
- const result = await fn();
423
- await queryRunner.commitTransaction();
424
- return result;
425
- } catch (error) {
426
- await queryRunner.rollbackTransaction();
427
- throw error;
428
- }
429
- }
430
-
431
- // EXTERNAL MODULE: external "@flusys/nestjs-core/config"
432
- var config_ = __webpack_require__(307);
433
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/request.util.ts
434
-
435
-
436
-
437
- /** Time unit multipliers in milliseconds */ const TIME_UNIT_MS = {
438
- s: 1000,
439
- m: 60 * 1000,
440
- h: 60 * 60 * 1000,
441
- d: 24 * 60 * 60 * 1000,
442
- w: 7 * 24 * 60 * 60 * 1000
443
- };
444
- /** Get normalized client type from request header */ function getClientType(req) {
445
- const clientType = req.headers[constants/* .CLIENT_TYPE_HEADER */.xw];
446
- return clientType ? clientType.toLowerCase() : null;
447
- }
448
- /**
449
- * Detect if request is from a browser client.
450
- * Uses x-client-type header first, then falls back to user-agent detection.
451
- */ function isBrowserRequest(req) {
452
- const clientType = getClientType(req);
453
- if (clientType) {
454
- return clientType === 'browser' || clientType === 'web';
455
- }
456
- const accept = req.headers['accept'] || '';
457
- if (accept.includes('text/html')) return true;
458
- const userAgent = req.headers['user-agent'] || '';
459
- const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
460
- return browserPatterns.test(userAgent) && !userAgent.includes('Postman');
461
- }
462
- /**
463
- * Build secure cookie options based on request context.
464
- * Handles HTTPS detection for TLS-terminating proxies.
465
- */ function buildCookieOptions(req) {
466
- const hostname = req.hostname || '';
467
- const origin = req.headers.origin || '';
468
- const isProduction = config_.envConfig.isProduction();
469
- const forwardedProto = req.headers['x-forwarded-proto'];
470
- const isHttps = isProduction || forwardedProto === 'https' || origin.startsWith('https://') || req.secure;
471
- let domain;
472
- const domainParts = hostname.split('.');
473
- if (domainParts.length > 2 && !hostname.includes('localhost')) {
474
- domain = '.' + domainParts.slice(-2).join('.');
475
- }
476
- return {
477
- secure: isHttps,
478
- sameSite: isHttps ? 'strict' : 'lax',
479
- ...domain && {
480
- domain
481
- }
482
- };
483
- }
484
- /**
485
- * Parse duration string to milliseconds.
486
- * @example '7d' → 604800000, '24h' → 86400000, '30m' → 1800000
487
- */ function parseDurationToMs(duration, defaultMs = TIME_UNIT_MS.w) {
488
- const match = duration.match(/^(\d+)(s|m|h|d|w)$/);
489
- if (!match) return defaultMs;
490
- const value = parseInt(match[1], 10);
491
- const unit = match[2];
492
- return value * (TIME_UNIT_MS[unit] ?? TIME_UNIT_MS.d);
493
- }
494
- /**
495
- * Determine the frontend base URL for auth-related links.
496
- * Matches the request's Origin/Referer against configured allowed origins.
497
- * Never trusts the request's Host header - it is client-controlled and would
498
- * let an attacker redirect password-reset/verification links to their own domain.
499
- */ function extractFrontendUrl(req) {
500
- const allowedOrigins = config_.envConfig.getOrigins();
501
- const origin = (req.get('origin') ?? req.get('referer') ?? '').trim();
502
- if (origin && allowedOrigins.length) {
503
- const matched = allowedOrigins.find((u)=>origin.startsWith(u));
504
- if (matched) {
505
- return matched;
506
- }
507
- }
508
- if (allowedOrigins.length) {
509
- return allowedOrigins[0];
510
- }
511
- throw new common_.BadRequestException({
512
- message: 'Unable to determine frontend URL from request',
513
- messageKey: constants/* .ERROR_MESSAGES.VALIDATION */.UU.VALIDATION
514
- });
515
- }
516
- function buildFrontendUrl(req, path) {
517
- const baseUrl = extractFrontendUrl(req);
518
- return `${baseUrl}${path}`;
519
- }
520
-
521
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/string.util.ts
522
- /**
523
- * String utility functions
524
- */ /**
525
- * Generate URL-friendly slug from a string
526
- * @param text - The text to convert to slug
527
- * @param maxLength - Maximum length of the slug (default: 100)
528
- * @returns URL-friendly slug
529
- *
530
- * @example
531
- * generateSlug('My Company Name') // 'my-company-name'
532
- * generateSlug('Hello World!') // 'hello-world'
533
- */ function generateSlug(text, maxLength = 100) {
534
- return text.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, '') // Remove special characters
535
- .replace(/\s+/g, '-') // Replace spaces with hyphens
536
- .replace(/-+/g, '-') // Replace multiple hyphens with single
537
- .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
538
- .substring(0, maxLength);
539
- }
540
- /**
541
- * Generate unique slug using single database query (optimized for large datasets)
542
- * Appends numeric suffix if collision exists (e.g., 'my-company-1', 'my-company-2')
543
- *
544
- * Performance: O(1) database query regardless of collision count
545
- * - Works efficiently with millions of records
546
- * - Requires index on slug column for best performance
547
- *
548
- * @param text - The text to convert to slug
549
- * @param findMatchingSlugs - Async function that returns all slugs matching base pattern
550
- * Should query: WHERE slug = baseSlug OR slug LIKE 'baseSlug-%'
551
- * @param maxLength - Maximum length of the base slug (default: 100)
552
- * @returns Unique URL-friendly slug
553
- *
554
- * @example
555
- * const slug = await generateUniqueSlug('My Company', async (baseSlug) => {
556
- * return repo
557
- * .createQueryBuilder('c')
558
- * .select('c.slug')
559
- * .where('c.slug = :base OR c.slug LIKE :pattern', {
560
- * base: baseSlug,
561
- * pattern: `${baseSlug}-%`,
562
- * })
563
- * .getMany()
564
- * .then(rows => rows.map(r => r.slug));
565
- * });
566
- */ async function generateUniqueSlug(text, findMatchingSlugs, maxLength = 100) {
567
- const baseSlug = generateSlug(text, maxLength);
568
- // Single query to get all matching slugs
569
- const existingSlugs = await findMatchingSlugs(baseSlug);
570
- // No collisions - base slug is available
571
- if (existingSlugs.length === 0) {
572
- return baseSlug;
573
- }
574
- // Build set for O(1) lookup
575
- const existingSet = new Set(existingSlugs);
576
- // Base slug not taken (only suffixed versions exist)
577
- if (!existingSet.has(baseSlug)) {
578
- return baseSlug;
579
- }
580
- // Find next available suffix in memory (fast)
581
- let counter = 1;
582
- while(counter < 10000){
583
- const slugWithSuffix = `${baseSlug}-${counter}`;
584
- if (!existingSet.has(slugWithSuffix)) {
585
- return slugWithSuffix;
586
- }
587
- counter++;
588
- }
589
- // Fallback: append random string (extremely rare edge case)
590
- const randomSuffix = Math.random().toString(36).substring(2, 8);
591
- return `${baseSlug}-${randomSuffix}`;
592
- }
593
-
594
- // EXTERNAL MODULE: external "crypto"
595
- var external_crypto_ = __webpack_require__(823);
596
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/token.util.ts
597
-
598
- /** 32 random bytes as hex, suitable for a password-reset/OTP/access-style token */ function generateSecureToken() {
599
- return external_crypto_.randomBytes(32).toString('hex');
600
- }
601
- /** SHA-256 hash of a token, for storing/comparing tokens without keeping the raw value */ function hashToken(value) {
602
- return external_crypto_.createHash('sha256').update(value).digest('hex');
603
- }
604
- function isExpired(expiresAt) {
605
- return expiresAt < new Date();
606
- }
607
-
608
- ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/index.ts
609
-
610
-
611
-
612
-
613
-
614
-
615
-
616
-
617
-
618
-
619
-
620
-
621
-
622
-
623
-
624
- },
625
- 368(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
626
- /**
627
- * Loads an optional peer dependency at runtime.
628
- *
629
- * The specifier has to stay invisible to the bundler. A plain `import(name)`
630
- * looks dynamic in source but webpack rewrites it into a context module rooted
631
- * at this directory - one that can only ever resolve files sitting next to this
632
- * one - so `amqplib` and `kafkajs` fail to load in the bundled app no matter how
633
- * correctly they are installed. Both lookups below are assembled at runtime,
634
- * where no bundler can rewrite them.
635
- *
636
- * `require` is used first because it is what the CommonJS builds actually run
637
- * on; native ESM has no `require`, and there `import()` is the only way in.
638
- */ async function loadOptionalModule(name) {
639
- const runtimeRequire = resolveRuntimeRequire();
640
- if (runtimeRequire) return runtimeRequire(name);
641
- const runtimeImport = new Function('specifier', 'return import(specifier);');
642
- return runtimeImport(name);
643
- }
644
- /** Undefined under native ESM and inside a bundle that shadows nothing back */ function resolveRuntimeRequire() {
645
- try {
646
- return eval('typeof require === "function" ? require : undefined');
647
- } catch {
648
- return undefined;
649
- }
650
- }
651
-
652
- __webpack_require__.d(__webpack_exports__, {
653
- l: () => (loadOptionalModule)
654
- });
655
-
656
-
657
- },
658
-
659
- };