@lenne.tech/nest-server 11.8.0 → 11.10.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 (173) hide show
  1. package/dist/config.env.js +5 -0
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/helpers/logging.helper.d.ts +6 -0
  4. package/dist/core/common/helpers/logging.helper.js +55 -0
  5. package/dist/core/common/helpers/logging.helper.js.map +1 -0
  6. package/dist/core/common/interfaces/server-options.interface.d.ts +50 -19
  7. package/dist/core/modules/auth/guards/roles.guard.js +37 -5
  8. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  9. package/dist/core/modules/auth/services/core-auth.service.d.ts +5 -5
  10. package/dist/core/modules/auth/services/core-auth.service.js +9 -8
  11. package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
  12. package/dist/core/modules/auth/tokens.decorator.d.ts +1 -1
  13. package/dist/core/modules/better-auth/better-auth.config.js +32 -10
  14. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  15. package/dist/core/modules/better-auth/better-auth.resolver.d.ts +16 -16
  16. package/dist/core/modules/better-auth/better-auth.resolver.js +34 -34
  17. package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
  18. package/dist/core/modules/better-auth/better-auth.types.d.ts +2 -1
  19. package/dist/core/modules/better-auth/better-auth.types.js.map +1 -1
  20. package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +10 -0
  21. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +91 -0
  22. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -0
  23. package/dist/core/modules/better-auth/core-better-auth-auth.model.d.ts +9 -0
  24. package/dist/core/modules/better-auth/{better-auth-auth.model.js → core-better-auth-auth.model.js} +17 -17
  25. package/dist/core/modules/better-auth/core-better-auth-auth.model.js.map +1 -0
  26. package/dist/core/modules/better-auth/{better-auth-migration-status.model.d.ts → core-better-auth-migration-status.model.d.ts} +1 -1
  27. package/dist/core/modules/better-auth/{better-auth-migration-status.model.js → core-better-auth-migration-status.model.js} +14 -14
  28. package/dist/core/modules/better-auth/core-better-auth-migration-status.model.js.map +1 -0
  29. package/dist/core/modules/better-auth/{better-auth-models.d.ts → core-better-auth-models.d.ts} +8 -8
  30. package/dist/core/modules/better-auth/{better-auth-models.js → core-better-auth-models.js} +61 -61
  31. package/dist/core/modules/better-auth/core-better-auth-models.js.map +1 -0
  32. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.d.ts +12 -0
  33. package/dist/core/modules/better-auth/{better-auth-rate-limit.middleware.js → core-better-auth-rate-limit.middleware.js} +10 -10
  34. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -0
  35. package/dist/core/modules/better-auth/{better-auth-rate-limiter.service.d.ts → core-better-auth-rate-limiter.service.d.ts} +1 -1
  36. package/dist/core/modules/better-auth/{better-auth-rate-limiter.service.js → core-better-auth-rate-limiter.service.js} +8 -8
  37. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -0
  38. package/dist/core/modules/better-auth/{better-auth-user.mapper.d.ts → core-better-auth-user.mapper.d.ts} +1 -1
  39. package/dist/core/modules/better-auth/{better-auth-user.mapper.js → core-better-auth-user.mapper.js} +10 -9
  40. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -0
  41. package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +19 -0
  42. package/dist/core/modules/better-auth/core-better-auth-web.helper.js +152 -0
  43. package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -0
  44. package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +23 -32
  45. package/dist/core/modules/better-auth/core-better-auth.controller.js +184 -201
  46. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  47. package/dist/core/modules/better-auth/core-better-auth.middleware.d.ts +22 -0
  48. package/dist/core/modules/better-auth/{better-auth.middleware.js → core-better-auth.middleware.js} +45 -18
  49. package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -0
  50. package/dist/core/modules/better-auth/{better-auth.module.d.ts → core-better-auth.module.d.ts} +6 -6
  51. package/dist/core/modules/better-auth/{better-auth.module.js → core-better-auth.module.js} +65 -60
  52. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -0
  53. package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +19 -19
  54. package/dist/core/modules/better-auth/core-better-auth.resolver.js +18 -18
  55. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  56. package/dist/core/modules/better-auth/{better-auth.service.d.ts → core-better-auth.service.d.ts} +3 -2
  57. package/dist/core/modules/better-auth/{better-auth.service.js → core-better-auth.service.js} +75 -35
  58. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -0
  59. package/dist/core/modules/better-auth/index.d.ts +11 -9
  60. package/dist/core/modules/better-auth/index.js +11 -9
  61. package/dist/core/modules/better-auth/index.js.map +1 -1
  62. package/dist/core/modules/error-code/core-error-code.controller.d.ts +7 -0
  63. package/dist/core/modules/error-code/core-error-code.controller.js +45 -0
  64. package/dist/core/modules/error-code/core-error-code.controller.js.map +1 -0
  65. package/dist/core/modules/error-code/core-error-code.service.d.ts +16 -0
  66. package/dist/core/modules/error-code/core-error-code.service.js +65 -0
  67. package/dist/core/modules/error-code/core-error-code.service.js.map +1 -0
  68. package/dist/core/modules/error-code/error-code.module.d.ts +7 -0
  69. package/dist/core/modules/error-code/error-code.module.js +64 -0
  70. package/dist/core/modules/error-code/error-code.module.js.map +1 -0
  71. package/dist/core/modules/error-code/error-codes.d.ts +219 -0
  72. package/dist/core/modules/error-code/error-codes.js +204 -0
  73. package/dist/core/modules/error-code/error-codes.js.map +1 -0
  74. package/dist/core/modules/error-code/index.d.ts +5 -0
  75. package/dist/core/modules/error-code/index.js +22 -0
  76. package/dist/core/modules/error-code/index.js.map +1 -0
  77. package/dist/core/modules/error-code/interfaces/error-code.interfaces.d.ts +12 -0
  78. package/dist/core/modules/error-code/interfaces/error-code.interfaces.js +3 -0
  79. package/dist/core/modules/error-code/interfaces/error-code.interfaces.js.map +1 -0
  80. package/dist/core/modules/user/interfaces/core-user-service-options.interface.d.ts +2 -2
  81. package/dist/core.module.js +14 -6
  82. package/dist/core.module.js.map +1 -1
  83. package/dist/index.d.ts +2 -0
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/server/modules/better-auth/better-auth.controller.d.ts +5 -5
  87. package/dist/server/modules/better-auth/better-auth.controller.js +4 -4
  88. package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -1
  89. package/dist/server/modules/better-auth/better-auth.module.js +3 -3
  90. package/dist/server/modules/better-auth/better-auth.module.js.map +1 -1
  91. package/dist/server/modules/better-auth/better-auth.resolver.d.ts +17 -17
  92. package/dist/server/modules/better-auth/better-auth.resolver.js +18 -18
  93. package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
  94. package/dist/server/modules/error-code/error-code.controller.d.ts +8 -0
  95. package/dist/server/modules/error-code/error-code.controller.js +55 -0
  96. package/dist/server/modules/error-code/error-code.controller.js.map +1 -0
  97. package/dist/server/modules/error-code/error-code.service.d.ts +4 -0
  98. package/dist/server/modules/error-code/error-code.service.js +27 -0
  99. package/dist/server/modules/error-code/error-code.service.js.map +1 -0
  100. package/dist/server/modules/error-code/error-codes.d.ts +45 -0
  101. package/dist/server/modules/error-code/error-codes.js +24 -0
  102. package/dist/server/modules/error-code/error-codes.js.map +1 -0
  103. package/dist/server/modules/error-code/index.d.ts +3 -0
  104. package/dist/server/modules/error-code/index.js +20 -0
  105. package/dist/server/modules/error-code/index.js.map +1 -0
  106. package/dist/server/modules/user/user.service.d.ts +2 -2
  107. package/dist/server/modules/user/user.service.js +2 -2
  108. package/dist/server/modules/user/user.service.js.map +1 -1
  109. package/dist/server/server.module.js +7 -0
  110. package/dist/server/server.module.js.map +1 -1
  111. package/dist/test/test.helper.d.ts +1 -0
  112. package/dist/test/test.helper.js +5 -1
  113. package/dist/test/test.helper.js.map +1 -1
  114. package/dist/tsconfig.build.tsbuildinfo +1 -1
  115. package/package.json +6 -4
  116. package/src/config.env.ts +19 -0
  117. package/src/core/common/helpers/logging.helper.ts +134 -0
  118. package/src/core/common/interfaces/server-options.interface.ts +511 -237
  119. package/src/core/modules/auth/guards/roles.guard.ts +49 -7
  120. package/src/core/modules/auth/services/core-auth.service.ts +9 -8
  121. package/src/core/modules/better-auth/ARCHITECTURE.md +102 -0
  122. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +277 -8
  123. package/src/core/modules/better-auth/README.md +97 -53
  124. package/src/core/modules/better-auth/better-auth.config.ts +66 -18
  125. package/src/core/modules/better-auth/better-auth.resolver.ts +32 -32
  126. package/src/core/modules/better-auth/better-auth.types.ts +3 -2
  127. package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +134 -0
  128. package/src/core/modules/better-auth/{better-auth-auth.model.ts → core-better-auth-auth.model.ts} +6 -6
  129. package/src/core/modules/better-auth/{better-auth-migration-status.model.ts → core-better-auth-migration-status.model.ts} +1 -1
  130. package/src/core/modules/better-auth/{better-auth-models.ts → core-better-auth-models.ts} +9 -9
  131. package/src/core/modules/better-auth/{better-auth-rate-limit.middleware.ts → core-better-auth-rate-limit.middleware.ts} +5 -5
  132. package/src/core/modules/better-auth/{better-auth-rate-limiter.service.ts → core-better-auth-rate-limiter.service.ts} +2 -2
  133. package/src/core/modules/better-auth/{better-auth-user.mapper.ts → core-better-auth-user.mapper.ts} +4 -3
  134. package/src/core/modules/better-auth/core-better-auth-web.helper.ts +272 -0
  135. package/src/core/modules/better-auth/core-better-auth.controller.ts +386 -230
  136. package/src/core/modules/better-auth/{better-auth.middleware.ts → core-better-auth.middleware.ts} +57 -17
  137. package/src/core/modules/better-auth/{better-auth.module.ts → core-better-auth.module.ts} +77 -66
  138. package/src/core/modules/better-auth/core-better-auth.resolver.ts +42 -42
  139. package/src/core/modules/better-auth/{better-auth.service.ts → core-better-auth.service.ts} +86 -40
  140. package/src/core/modules/better-auth/index.ts +18 -11
  141. package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +291 -0
  142. package/src/core/modules/error-code/core-error-code.controller.ts +55 -0
  143. package/src/core/modules/error-code/core-error-code.service.ts +135 -0
  144. package/src/core/modules/error-code/error-code.module.ts +119 -0
  145. package/src/core/modules/error-code/error-codes.ts +405 -0
  146. package/src/core/modules/error-code/index.ts +14 -0
  147. package/src/core/modules/error-code/interfaces/error-code.interfaces.ts +99 -0
  148. package/src/core/modules/user/interfaces/core-user-service-options.interface.ts +3 -3
  149. package/src/core.module.ts +28 -12
  150. package/src/index.ts +7 -0
  151. package/src/server/modules/better-auth/better-auth.controller.ts +4 -4
  152. package/src/server/modules/better-auth/better-auth.module.ts +1 -1
  153. package/src/server/modules/better-auth/better-auth.resolver.ts +31 -31
  154. package/src/server/modules/error-code/README.md +131 -0
  155. package/src/server/modules/error-code/error-code.controller.ts +91 -0
  156. package/src/server/modules/error-code/error-code.service.ts +42 -0
  157. package/src/server/modules/error-code/error-codes.ts +65 -0
  158. package/src/server/modules/error-code/index.ts +8 -0
  159. package/src/server/modules/user/user.service.ts +2 -2
  160. package/src/server/server.module.ts +10 -0
  161. package/src/test/test.helper.ts +13 -1
  162. package/dist/core/modules/better-auth/better-auth-auth.model.d.ts +0 -9
  163. package/dist/core/modules/better-auth/better-auth-auth.model.js.map +0 -1
  164. package/dist/core/modules/better-auth/better-auth-migration-status.model.js.map +0 -1
  165. package/dist/core/modules/better-auth/better-auth-models.js.map +0 -1
  166. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.d.ts +0 -12
  167. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js.map +0 -1
  168. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js.map +0 -1
  169. package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +0 -1
  170. package/dist/core/modules/better-auth/better-auth.middleware.d.ts +0 -21
  171. package/dist/core/modules/better-auth/better-auth.middleware.js.map +0 -1
  172. package/dist/core/modules/better-auth/better-auth.module.js.map +0 -1
  173. package/dist/core/modules/better-auth/better-auth.service.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.8.0",
3
+ "version": "11.10.0",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -21,7 +21,7 @@
21
21
  "build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
22
22
  "build:dev": "npm run build && yalc push --private",
23
23
  "docs": "npm run docs:ci && open http://127.0.0.1:8080/ && open ./public/index.html && compodoc -p tsconfig.json -s ",
24
- "docs:bootstrap": "node extras/update-spectaql-version.mjs && npx -y spectaql ./spectaql.yml",
24
+ "docs:bootstrap": "node extras/update-spectaql-version.mjs && node scripts/run-spectaql.mjs",
25
25
  "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
26
26
  "format": "prettier --write 'src/**/*.ts'",
27
27
  "format:staged": "pretty-quick --staged",
@@ -62,6 +62,7 @@
62
62
  "vitest:watch": "NODE_ENV=local vitest --config vitest-e2e.config.ts",
63
63
  "vitest:unit": "vitest run --config vitest.config.ts",
64
64
  "test:unit:watch": "vitest --config vitest.config.ts",
65
+ "test:types": "tsc --noEmit --skipLibCheck -p tests/types/tsconfig.json",
65
66
  "watch": "npm-watch",
66
67
  "link:eslint": "yalc add @lenne.tech/eslint-config-ts && yalc link @lenne.tech/eslint-config-ts && npm install",
67
68
  "unlink:eslint": "yalc remove @lenne.tech/eslint-config-ts && npm install"
@@ -79,7 +80,7 @@
79
80
  "dependencies": {
80
81
  "@apollo/server": "5.2.0",
81
82
  "@as-integrations/express5": "1.1.2",
82
- "@better-auth/passkey": "1.4.8-beta.4",
83
+ "@better-auth/passkey": "^1.4.16",
83
84
  "@getbrevo/brevo": "3.0.1",
84
85
  "@nestjs/apollo": "13.2.3",
85
86
  "@nestjs/common": "11.1.9",
@@ -97,7 +98,7 @@
97
98
  "@tus/server": "2.3.0",
98
99
  "apollo-server-core": "3.13.0",
99
100
  "bcrypt": "6.0.0",
100
- "better-auth": "1.4.8-beta.4",
101
+ "better-auth": "^1.4.16",
101
102
  "class-transformer": "0.5.1",
102
103
  "class-validator": "0.14.3",
103
104
  "compression": "1.8.1",
@@ -146,6 +147,7 @@
146
147
  "@typescript-eslint/parser": "8.50.0",
147
148
  "@vitest/coverage-v8": "4.0.16",
148
149
  "@vitest/ui": "4.0.16",
150
+ "otpauth": "9.4.1",
149
151
  "ansi-colors": "4.1.3",
150
152
  "eslint": "9.39.2",
151
153
  "eslint-config-prettier": "10.1.8",
package/src/config.env.ts CHANGED
@@ -69,6 +69,12 @@ const config: { [env: string]: IServerOptions } = {
69
69
  appName: 'Nest Server Development',
70
70
  enabled: false,
71
71
  },
72
+ // CORS trustedOrigins configuration:
73
+ // - Not set + Passkey disabled: All origins allowed (default)
74
+ // - Not set + Passkey enabled: Server startup FAILS (trustedOrigins required)
75
+ // - Set explicitly: Only configured origins allowed
76
+ // Uncomment and configure when enabling Passkey:
77
+ // trustedOrigins: ['http://localhost:3000', 'http://localhost:3001'],
72
78
  },
73
79
  compression: true,
74
80
  cookies: false,
@@ -223,6 +229,10 @@ const config: { [env: string]: IServerOptions } = {
223
229
  enabled: false,
224
230
  },
225
231
  },
232
+ // REQUIRED when Passkey is enabled!
233
+ // Passkey uses credentials: 'include' which requires explicit CORS origins.
234
+ // Server startup will fail if Passkey is enabled without trustedOrigins.
235
+ trustedOrigins: ['http://localhost:3000', 'http://localhost:3001'],
226
236
  twoFactor: {
227
237
  appName: 'Nest Server Local',
228
238
  enabled: true,
@@ -262,6 +272,10 @@ const config: { [env: string]: IServerOptions } = {
262
272
  verificationLink: 'http://localhost:4200/user/verification',
263
273
  },
264
274
  env: 'local',
275
+ // Disable auto-registration to allow Server ErrorCodeModule with SRV_* codes
276
+ errorCode: {
277
+ autoRegister: false,
278
+ },
265
279
  execAfterInit: 'npm run docs:bootstrap',
266
280
  filter: {
267
281
  maxLimit: null,
@@ -395,10 +409,15 @@ const config: { [env: string]: IServerOptions } = {
395
409
  enabled: !!process.env.SOCIAL_GOOGLE_CLIENT_ID,
396
410
  },
397
411
  },
412
+ // REQUIRED for Passkey in production!
413
+ // Passkey uses credentials: 'include' which requires explicit origins (no wildcard '*')
414
+ // Configure all frontend URLs that need Passkey authentication:
415
+ trustedOrigins: process.env.TRUSTED_ORIGINS?.split(',') || [],
398
416
  twoFactor: {
399
417
  appName: process.env.TWO_FACTOR_APP_NAME || 'Nest Server',
400
418
  enabled: process.env.TWO_FACTOR_ENABLED === 'true',
401
419
  },
420
+ // Example: TRUSTED_ORIGINS=https://app.example.com,https://admin.example.com
402
421
  },
403
422
  compression: true,
404
423
  cookies: false,
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Logging helper functions for safe logging of sensitive data.
3
+ *
4
+ * These functions help mask sensitive information in logs to comply with
5
+ * security best practices and GDPR requirements.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { maskToken, maskEmail, maskSensitive } from './logging.helper';
10
+ *
11
+ * // Instead of: logger.debug(`Token: ${token}`)
12
+ * logger.debug(`Token: ${maskToken(token)}`);
13
+ *
14
+ * // Instead of: logger.debug(`User: ${user.email}`)
15
+ * logger.debug(`User: ${maskEmail(user.email)}`);
16
+ * ```
17
+ */
18
+
19
+ /**
20
+ * Checks if the current environment is production.
21
+ * Use this to conditionally skip verbose debug logging in production.
22
+ *
23
+ * @returns true if NODE_ENV is 'production'
24
+ */
25
+ export function isProduction(): boolean {
26
+ return process.env.NODE_ENV === 'production';
27
+ }
28
+
29
+ /**
30
+ * Masks a cookie header for safe logging.
31
+ * Removes all cookie values, keeping only cookie names.
32
+ *
33
+ * @param cookieHeader - The cookie header string
34
+ * @returns Masked cookie header showing only cookie names
35
+ *
36
+ * @example
37
+ * maskCookieHeader('session=abc123; token=xyz789') // 'session=***; token=***'
38
+ */
39
+ export function maskCookieHeader(cookieHeader: null | string | undefined): string {
40
+ if (!cookieHeader) {
41
+ return 'none';
42
+ }
43
+ // Replace cookie values with ***
44
+ return cookieHeader.replace(/=([^;]*)/g, '=***');
45
+ }
46
+
47
+ /**
48
+ * Masks an email address for safe logging.
49
+ * Shows only the first 2 characters of the local part and the domain.
50
+ *
51
+ * @param email - The email to mask
52
+ * @returns Masked email or 'none' if not provided
53
+ *
54
+ * @example
55
+ * maskEmail('john.doe@example.com') // 'jo***@example.com'
56
+ * maskEmail(null) // 'none'
57
+ */
58
+ export function maskEmail(email: null | string | undefined): string {
59
+ if (!email) {
60
+ return 'none';
61
+ }
62
+ const atIndex = email.indexOf('@');
63
+ if (atIndex <= 0) {
64
+ return '***';
65
+ }
66
+ const localPart = email.substring(0, atIndex);
67
+ const domain = email.substring(atIndex);
68
+ const visibleChars = Math.min(2, localPart.length);
69
+ return `${localPart.substring(0, visibleChars)}***${domain}`;
70
+ }
71
+
72
+ /**
73
+ * Masks an ObjectId or ID string for safe logging.
74
+ *
75
+ * @param id - The ID to mask
76
+ * @returns Masked ID or 'none' if not provided
77
+ *
78
+ * @example
79
+ * maskId('507f1f77bcf86cd799439011') // '507f***9011'
80
+ */
81
+ export function maskId(id: null | string | undefined): string {
82
+ return maskSensitive(id, 4, 4);
83
+ }
84
+
85
+ /**
86
+ * Masks sensitive string data for safe logging.
87
+ * Generic function for any sensitive string.
88
+ *
89
+ * @param value - The value to mask
90
+ * @param visibleStart - Number of characters to show at start (default: 4)
91
+ * @param visibleEnd - Number of characters to show at end (default: 0)
92
+ * @returns Masked value or 'none' if not provided
93
+ *
94
+ * @example
95
+ * maskSensitive('secretpassword123') // 'secr***'
96
+ * maskSensitive('secretpassword123', 2, 2) // 'se***23'
97
+ */
98
+ export function maskSensitive(
99
+ value: null | string | undefined,
100
+ visibleStart: number = 4,
101
+ visibleEnd: number = 0,
102
+ ): string {
103
+ if (!value) {
104
+ return 'none';
105
+ }
106
+ const minLength = visibleStart + visibleEnd + 3; // At least 3 chars for '***'
107
+ if (value.length <= minLength) {
108
+ return '***';
109
+ }
110
+ const start = value.substring(0, visibleStart);
111
+ const end = visibleEnd > 0 ? value.substring(value.length - visibleEnd) : '';
112
+ return `${start}***${end}`;
113
+ }
114
+
115
+ /**
116
+ * Masks a token for safe logging.
117
+ * Shows only the first 4 and last 4 characters.
118
+ *
119
+ * @param token - The token to mask
120
+ * @returns Masked token or 'none' if not provided
121
+ *
122
+ * @example
123
+ * maskToken('abc123xyz789') // 'abc1...9'
124
+ * maskToken(null) // 'none'
125
+ */
126
+ export function maskToken(token: null | string | undefined): string {
127
+ if (!token) {
128
+ return 'none';
129
+ }
130
+ if (token.length <= 8) {
131
+ return '***';
132
+ }
133
+ return `${token.substring(0, 4)}...${token.substring(token.length - 4)}`;
134
+ }