@wildo-ai/saas-backend-lib 1.1.0 → 1.1.2

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 (265) hide show
  1. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts +14 -3
  2. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts.map +1 -1
  3. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js +115 -22
  4. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js.map +1 -1
  5. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts +3 -1
  6. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts.map +1 -1
  7. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js +8 -3
  8. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js.map +1 -1
  9. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts +11 -0
  10. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts.map +1 -1
  11. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js +11 -2
  12. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js.map +1 -1
  13. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts +1 -0
  14. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts.map +1 -1
  15. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js +6 -1
  16. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js.map +1 -1
  17. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.d.ts.map +1 -1
  18. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js +16 -0
  19. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js.map +1 -1
  20. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts +20 -6
  21. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts.map +1 -1
  22. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js +88 -32
  23. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js.map +1 -1
  24. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts +3 -1
  25. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts.map +1 -1
  26. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js +32 -7
  27. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js.map +1 -1
  28. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts +8 -5
  29. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts.map +1 -1
  30. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.js.map +1 -1
  31. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts +31 -0
  32. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts.map +1 -1
  33. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js +127 -5
  34. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js.map +1 -1
  35. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts +71 -1
  36. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts.map +1 -1
  37. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js +62 -2
  38. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js.map +1 -1
  39. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts +21 -0
  40. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts.map +1 -1
  41. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js +126 -4
  42. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js.map +1 -1
  43. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts +3 -26
  44. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts.map +1 -1
  45. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js +3 -68
  46. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js.map +1 -1
  47. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
  48. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +4 -2
  49. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
  50. package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js +6 -6
  51. package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js.map +1 -1
  52. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts +38 -0
  53. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts.map +1 -1
  54. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js +40 -0
  55. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js.map +1 -1
  56. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts +20 -0
  57. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts.map +1 -1
  58. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js +28 -0
  59. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js.map +1 -1
  60. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts +12 -0
  61. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts.map +1 -0
  62. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js +118 -0
  63. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js.map +1 -0
  64. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.d.ts.map +1 -1
  65. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js +8 -5
  66. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js.map +1 -1
  67. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.d.ts.map +1 -1
  68. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js +14 -0
  69. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js.map +1 -1
  70. package/dist/esm/config/custom/application-config-controller.backend.service.d.ts.map +1 -1
  71. package/dist/esm/config/custom/application-config-controller.backend.service.js +7 -1
  72. package/dist/esm/config/custom/application-config-controller.backend.service.js.map +1 -1
  73. package/dist/esm/config/models/app-config.backend.schemas.d.ts +4 -0
  74. package/dist/esm/config/models/app-config.backend.schemas.d.ts.map +1 -1
  75. package/dist/esm/config/services/app-configuration.backend.utils.d.ts +10 -1
  76. package/dist/esm/config/services/app-configuration.backend.utils.d.ts.map +1 -1
  77. package/dist/esm/config/services/app-configuration.backend.utils.js +13 -2
  78. package/dist/esm/config/services/app-configuration.backend.utils.js.map +1 -1
  79. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.d.ts +2 -2
  80. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js +2 -2
  81. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js.map +1 -1
  82. package/dist/esm/databases/models/filter.backend.schemas.js +2 -2
  83. package/dist/esm/databases/models/filter.backend.schemas.js.map +1 -1
  84. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.d.ts.map +1 -1
  85. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js +20 -1
  86. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js.map +1 -1
  87. package/dist/esm/databases/services/redis.service.d.ts +27 -0
  88. package/dist/esm/databases/services/redis.service.d.ts.map +1 -1
  89. package/dist/esm/databases/services/redis.service.js +29 -0
  90. package/dist/esm/databases/services/redis.service.js.map +1 -1
  91. package/dist/esm/errors/services/error-handler.backend.service.d.ts.map +1 -1
  92. package/dist/esm/errors/services/error-handler.backend.service.js +5 -0
  93. package/dist/esm/errors/services/error-handler.backend.service.js.map +1 -1
  94. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts +41 -0
  95. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts.map +1 -0
  96. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js +40 -0
  97. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js.map +1 -0
  98. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts +64 -0
  99. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts.map +1 -0
  100. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js +193 -0
  101. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js.map +1 -0
  102. package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts +13 -2
  103. package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts.map +1 -1
  104. package/dist/esm/external-data/external-data-pipeline-run.backend.service.js +8 -2
  105. package/dist/esm/external-data/external-data-pipeline-run.backend.service.js.map +1 -1
  106. package/dist/esm/external-data/external-data-transform.backend.d.ts.map +1 -1
  107. package/dist/esm/external-data/external-data-transform.backend.js +7 -1
  108. package/dist/esm/external-data/external-data-transform.backend.js.map +1 -1
  109. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts +2 -0
  110. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts.map +1 -1
  111. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js +13 -0
  112. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js.map +1 -1
  113. package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +14 -0
  114. package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
  115. package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +6 -0
  116. package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
  117. package/dist/esm/files/controllers/file.controller.d.ts +24 -1
  118. package/dist/esm/files/controllers/file.controller.d.ts.map +1 -1
  119. package/dist/esm/files/controllers/file.controller.js +66 -2
  120. package/dist/esm/files/controllers/file.controller.js.map +1 -1
  121. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts +17 -0
  122. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts.map +1 -0
  123. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js +1 -0
  124. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js.map +1 -0
  125. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts +5 -1
  126. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts.map +1 -1
  127. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js +42 -3
  128. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js.map +1 -1
  129. package/dist/esm/files/services/file-operation-handler.backend.service.d.ts +18 -0
  130. package/dist/esm/files/services/file-operation-handler.backend.service.d.ts.map +1 -1
  131. package/dist/esm/files/services/file-operation-handler.backend.service.js +36 -28
  132. package/dist/esm/files/services/file-operation-handler.backend.service.js.map +1 -1
  133. package/dist/esm/files/services/file-upload-grant.backend.service.d.ts +163 -0
  134. package/dist/esm/files/services/file-upload-grant.backend.service.d.ts.map +1 -0
  135. package/dist/esm/files/services/file-upload-grant.backend.service.js +442 -0
  136. package/dist/esm/files/services/file-upload-grant.backend.service.js.map +1 -0
  137. package/dist/esm/files/services/files.backend.service.d.ts +19 -0
  138. package/dist/esm/files/services/files.backend.service.d.ts.map +1 -1
  139. package/dist/esm/files/services/files.backend.service.js +78 -15
  140. package/dist/esm/files/services/files.backend.service.js.map +1 -1
  141. package/dist/esm/files/utils/file-uploader.utils.d.ts.map +1 -1
  142. package/dist/esm/files/utils/file-uploader.utils.js +12 -0
  143. package/dist/esm/files/utils/file-uploader.utils.js.map +1 -1
  144. package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js +5 -5
  145. package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js.map +1 -1
  146. package/dist/esm/flows-actors/services/agents.backend.service.d.ts +79 -0
  147. package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
  148. package/dist/esm/flows-actors/services/agents.backend.service.js +319 -42
  149. package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
  150. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +151 -41
  151. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
  152. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +300 -139
  153. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
  154. package/dist/esm/index.d.ts +4 -1
  155. package/dist/esm/index.d.ts.map +1 -1
  156. package/dist/esm/index.js +14 -1
  157. package/dist/esm/index.js.map +1 -1
  158. package/dist/esm/inject/models/container.d.ts.map +1 -1
  159. package/dist/esm/inject/models/container.js +4 -0
  160. package/dist/esm/inject/models/container.js.map +1 -1
  161. package/dist/esm/inject/models/service-types.d.ts +3 -0
  162. package/dist/esm/inject/models/service-types.d.ts.map +1 -1
  163. package/dist/esm/inject/models/service-types.js +2 -0
  164. package/dist/esm/inject/models/service-types.js.map +1 -1
  165. package/dist/esm/mcp/controllers/mcp.controller.d.ts.map +1 -1
  166. package/dist/esm/mcp/controllers/mcp.controller.js +12 -4
  167. package/dist/esm/mcp/controllers/mcp.controller.js.map +1 -1
  168. package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts +34 -0
  169. package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts.map +1 -1
  170. package/dist/esm/mcp/models/mcp-tool.backend.schemas.js +42 -0
  171. package/dist/esm/mcp/models/mcp-tool.backend.schemas.js.map +1 -1
  172. package/dist/esm/mcp/services/mcp-server.backend.service.d.ts +29 -1
  173. package/dist/esm/mcp/services/mcp-server.backend.service.d.ts.map +1 -1
  174. package/dist/esm/mcp/services/mcp-server.backend.service.js +131 -3
  175. package/dist/esm/mcp/services/mcp-server.backend.service.js.map +1 -1
  176. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts +64 -2
  177. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts.map +1 -1
  178. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js +130 -6
  179. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js.map +1 -1
  180. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts +9 -0
  181. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts.map +1 -1
  182. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js +11 -36
  183. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js.map +1 -1
  184. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.d.ts.map +1 -1
  185. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js +9 -7
  186. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js.map +1 -1
  187. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts +5 -5
  188. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts.map +1 -1
  189. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js +14 -13
  190. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js.map +1 -1
  191. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts +2 -1
  192. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts.map +1 -1
  193. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js +2 -2
  194. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js.map +1 -1
  195. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.d.ts.map +1 -1
  196. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js +9 -7
  197. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js.map +1 -1
  198. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.d.ts.map +1 -1
  199. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js +4 -1
  200. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js.map +1 -1
  201. package/dist/esm/requests/service/websocket.service.d.ts.map +1 -1
  202. package/dist/esm/requests/service/websocket.service.js +56 -7
  203. package/dist/esm/requests/service/websocket.service.js.map +1 -1
  204. package/dist/esm/resources/models/custom-service-implementations.backend.definition.d.ts.map +1 -1
  205. package/dist/esm/resources/models/custom-service-implementations.backend.definition.js +2 -0
  206. package/dist/esm/resources/models/custom-service-implementations.backend.definition.js.map +1 -1
  207. package/dist/esm/resources/services/resource-id.backend.service.js +3 -3
  208. package/dist/esm/resources/services/resource-id.backend.service.js.map +1 -1
  209. package/dist/esm/resources/services/resources-registry.backend.service.js +1 -1
  210. package/dist/esm/resources/services/resources-registry.backend.service.js.map +1 -1
  211. package/dist/esm/runtime/services/execution-context-creator.backend.service.d.ts.map +1 -1
  212. package/dist/esm/runtime/services/execution-context-creator.backend.service.js +13 -7
  213. package/dist/esm/runtime/services/execution-context-creator.backend.service.js.map +1 -1
  214. package/dist/esm/runtime/services/start-application.backend.service.d.ts +28 -0
  215. package/dist/esm/runtime/services/start-application.backend.service.d.ts.map +1 -1
  216. package/dist/esm/runtime/services/start-application.backend.service.js +82 -0
  217. package/dist/esm/runtime/services/start-application.backend.service.js.map +1 -1
  218. package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts +3 -23
  219. package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts.map +1 -1
  220. package/dist/esm/security/authentications/models/jwt.backend.schemas.js +20 -45
  221. package/dist/esm/security/authentications/models/jwt.backend.schemas.js.map +1 -1
  222. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts +9 -1
  223. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts.map +1 -1
  224. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js +9 -1
  225. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js.map +1 -1
  226. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.d.ts.map +1 -1
  227. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js +5 -3
  228. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js.map +1 -1
  229. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts +25 -1
  230. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts.map +1 -1
  231. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js +55 -4
  232. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js.map +1 -1
  233. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.d.ts.map +1 -1
  234. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js +24 -1
  235. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js.map +1 -1
  236. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts +117 -0
  237. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts.map +1 -0
  238. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js +104 -0
  239. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js.map +1 -0
  240. package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts +101 -6
  241. package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts.map +1 -1
  242. package/dist/esm/security/authorizations/services/authorizations.backend.service.js +210 -30
  243. package/dist/esm/security/authorizations/services/authorizations.backend.service.js.map +1 -1
  244. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts +18 -1
  245. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts.map +1 -1
  246. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js +110 -74
  247. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js.map +1 -1
  248. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts +22 -0
  249. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts.map +1 -0
  250. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js +22 -0
  251. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js.map +1 -0
  252. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts +17 -3
  253. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts.map +1 -1
  254. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js +12 -0
  255. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js.map +1 -1
  256. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts +20 -0
  257. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts.map +1 -1
  258. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js +58 -5
  259. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js.map +1 -1
  260. package/dist/esm/security/jwt/services/jwt.backend.service.d.ts.map +1 -1
  261. package/dist/esm/security/jwt/services/jwt.backend.service.js +1 -18
  262. package/dist/esm/security/jwt/services/jwt.backend.service.js.map +1 -1
  263. package/dist/tsconfig.build.tsbuildinfo +1 -1
  264. package/package.json +15 -9
  265. package/dist/esm/.builder.pid +0 -9
@@ -1 +1 @@
1
- {"version":3,"file":"auth-mfa.backend.service.js","sourceRoot":"","sources":["../../../../../../../src/security/authentications/services/auth-methods/auth-mfa.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport crypto from 'crypto';\nimport { injectable, inject } from 'inversify';\nimport { TOTP, Secret } from 'otpauth';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport { AuthUserLookupBackendService } from '~wildo-backend-lib/security/authentications/services/utils/auth-user-lookup.backend.service';\nimport { AuthCredentialOpsBackendService } from '~wildo-backend-lib/security/authentications/services/utils/auth-credential-ops.backend.service';\nimport { RedisService } from '~wildo-backend-lib/databases/services/redis.service';\nimport { AUTH_REDIS_KEYS } from '~wildo-backend-lib/security/authentications/models/auth-constants.backend';\nimport { ErrorType, ErrorCustomMessageReference } from '@wildo-ai/saas-models';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\n\nexport interface TotpSetupResult {\n secret: string;\n qrCodeUri: string;\n backupCodes: string[];\n}\n\nexport interface TotpSetupParams {\n userId: string;\n email: string;\n ipAddress: string;\n userAgent: string;\n}\n\nexport interface TotpVerifySetupParams {\n userId: string;\n code: string;\n ipAddress: string;\n userAgent: string;\n}\n\n/**\n * Outcome of {@link AuthMfaBackendService.verifyEnrolledSecondFactor}.\n *\n * `usedBackupCode` is reported rather than folded into `valid` because a consumed backup code is a\n * materially different event from a TOTP success: it is single-use, it depletes a finite reserve,\n * and callers surface it (login returns it as `challengeData`).\n */\nexport interface EnrolledSecondFactorVerification {\n readonly valid: boolean;\n readonly usedBackupCode: boolean;\n /** Set when the principal has no usable enrolled second factor at all (no verified TOTP secret). */\n readonly notEnrolled: boolean;\n}\n\n/**\n * TOTP enrollment — setup and verify-setup.\n * Uses static `otpauth` import (cleanup from dynamic import in controller).\n */\n@injectable()\nexport class AuthMfaBackendService {\n private readonly logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) private readonly appConfigService: AppConfigurationService,\n @inject(SAAS_SERVICE_TYPES.AuditLogsService) private readonly auditLogsService: AuditLogsBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthCredentialOpsService) private readonly credentialOps: AuthCredentialOpsBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthUserLookupService) private readonly userLookup: AuthUserLookupBackendService,\n // Appended LAST on purpose: a constructor parameter inserted in the middle silently re-binds\n // every following injection in this service's DI signature.\n @inject(SAAS_SERVICE_TYPES.RedisService) private readonly redisService: RedisService,\n ) {\n this.logDebug = this.logger.debugByType('AuthMfa');\n this.logger.setDebugTypeLogging('AuthMfa', false);\n }\n\n /**\n * Verify an ALREADY-ENROLLED second factor: a TOTP code, or one of the principal's backup codes.\n *\n * # Why this is a shared primitive rather than per-caller logic\n *\n * Two callers need exactly this question answered — the login second-factor step\n * (`authentication-orchestrator`) and step-up re-authentication\n * (`auth-method-management.reAuthenticate`) — and the answer carries three protections that a\n * naive `totp.validate()` does not:\n *\n * - **Anti-replay (RFC 6238 §5.2).** A TOTP code stays valid for a whole time step plus the\n * configured drift window, so without this a code observed once can be presented again inside\n * that window. The absolute time step is recorded in Redis and anything at or below it is\n * refused. This is precisely what makes step-up worth having: a code shoulder-surfed during\n * login must NOT also authorize a destructive operation moments later.\n * - **Backup-code fallback with constant-time comparison**, iterating every stored hash so a\n * match position cannot leak through timing, and consuming the code on success.\n * - **The enrollment guard** — an unverified or absent secret is `notEnrolled`, distinct from a\n * wrong code, so callers can tell \"you have no second factor\" from \"that code is wrong\".\n *\n * Duplicating any of this per caller would create a second, weaker door onto the same credential.\n * Callers own their own reactions (session attempt counting, audit events, error shapes); this\n * method owns only the cryptographic and replay question and NEVER throws for a bad code.\n *\n * assurance-control: WILDO.IDENTITY.MULTI_FACTOR — one verification authority for enrolled second\n * factors, replay-resistant and timing-safe.\n */\n public async verifyEnrolledSecondFactor(\n ec: ExecutionContext<any>,\n userId: string,\n token: string,\n ): Promise<EnrolledSecondFactorVerification> {\n const cred = await this.credentialOps.readByUserId(ec, userId);\n\n if (!cred || !cred.totpVerified || !cred.secretBase32) {\n return { valid: false, usedBackupCode: false, notEnrolled: true };\n }\n\n const { algorithm, digits, period, window } = this.appConfigService.config.auth.totpOperational;\n const totp = new TOTP({ secret: cred.secretBase32, algorithm, digits, period });\n\n const delta = totp.validate({ token, window });\n let isValidTOTP = delta !== null;\n\n // Anti-replay: store the ABSOLUTE time step (current period + delta), not the relative delta —\n // a relative value stops being comparable the moment the period rolls over.\n if (isValidTOTP) {\n const currentTimeStep = Math.floor(Date.now() / (period * 1000));\n const absoluteStep = currentTimeStep + delta!;\n const replayKey = `${AUTH_REDIS_KEYS.TOTP_USED}${userId}`;\n const lastUsedStep = await this.redisService.get<string>(replayKey);\n if (lastUsedStep !== null && absoluteStep <= parseInt(lastUsedStep as string, 10)) {\n isValidTOTP = false;\n } else {\n await this.redisService.set(replayKey, String(absoluteStep), { ttl: 90 });\n }\n }\n\n if (isValidTOTP) return { valid: true, usedBackupCode: false, notEnrolled: false };\n\n if (cred.backupCodesHashed?.length > 0) {\n const inputHash = crypto.createHash('sha256').update(token).digest('hex');\n const inputBuf = Buffer.from(inputHash, 'hex');\n\n let matchIndex = -1;\n for (let i = 0; i < cred.backupCodesHashed.length; i++) {\n const storedBuf = Buffer.from(cred.backupCodesHashed[i], 'hex');\n if (inputBuf.length === storedBuf.length && crypto.timingSafeEqual(inputBuf, storedBuf)) {\n matchIndex = i;\n // Deliberately NO `break` — always compare every hash so the match position cannot leak.\n }\n }\n\n if (matchIndex >= 0) {\n const updatedCodes = [...cred.backupCodesHashed];\n updatedCodes.splice(matchIndex, 1);\n await this.credentialOps.updateBackupCodes(ec, cred._id, updatedCodes);\n return { valid: true, usedBackupCode: true, notEnrolled: false };\n }\n }\n\n return { valid: false, usedBackupCode: false, notEnrolled: false };\n }\n\n async setupTOTP(ec: ExecutionContext<any>, params: TotpSetupParams): Promise<TotpSetupResult> {\n const { userId, ipAddress, userAgent } = params;\n\n this.logDebug('MFA TOTP setup requested', { userId });\n\n const cred = await this.credentialOps.readByUserId(ec, userId);\n\n if (!cred) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, undefined, {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_USER_CREDENTIALS_NOT_FOUND,\n });\n }\n\n if (cred.totpVerified) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.MFA_TOTP_ALREADY_ENROLLED,\n });\n }\n\n const secret = new Secret();\n const totpConfig = this.appConfigService.config.auth.totpOperational;\n const issuer = totpConfig.issuer || this.appConfigService.config.appIdentity.appName || 'Wildo';\n\n const accountName = await this.userLookup.getUserEmail(ec, userId) || userId;\n const label = totpConfig.includeIssuerInLabel ? accountName : accountName;\n\n const totp = new TOTP({\n issuer,\n label,\n algorithm: totpConfig.algorithm,\n digits: totpConfig.digits,\n period: totpConfig.period,\n secret,\n });\n const qrCodeUri = totp.toString();\n\n const backupCodes: string[] = [];\n const backupCodesHashed: string[] = [];\n for (let i = 0; i < 10; i++) {\n const code = crypto.randomBytes(4).toString('hex');\n backupCodes.push(code);\n backupCodesHashed.push(crypto.createHash('sha256').update(code).digest('hex'));\n }\n\n await this.credentialOps.writeTotpSetup(ec, cred._id, { secretBase32: secret.base32, backupCodesHashed });\n\n this.auditLogsService.logUserMFASetupStarted({ userId: userId, mfaMethod: 'totp', ipAddress, userAgent }, ec).catch(() => {});\n this.logDebug('TOTP secret generated on credentials, awaiting verification', { userId });\n\n return { secret: secret.base32, qrCodeUri, backupCodes };\n }\n\n async verifyTOTPSetup(ec: ExecutionContext<any>, params: TotpVerifySetupParams): Promise<{ verified: boolean }> {\n const { userId, code, ipAddress, userAgent } = params;\n\n this.logDebug('MFA TOTP verify-setup requested', { userId });\n\n const cred = await this.credentialOps.readByUserId(ec, userId);\n\n if (!cred || !cred.secretBase32 || cred.totpVerified) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, undefined, {\n customMessageReference: ErrorCustomMessageReference.MFA_TOTP_SETUP_NOT_INITIATED,\n });\n }\n\n const totpConfig = this.appConfigService.config.auth.totpOperational;\n const totp = new TOTP({\n secret: cred.secretBase32,\n algorithm: totpConfig.algorithm,\n digits: totpConfig.digits,\n period: totpConfig.period,\n });\n const delta = totp.validate({ token: code, window: totpConfig.window });\n if (delta === null) {\n throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.MFA_TOTP_CODE_INVALID,\n });\n }\n\n await this.credentialOps.confirmTotpSetup(ec, cred._id);\n\n this.auditLogsService.logUserMFASetupCompleted({ userId: userId, mfaMethod: 'totp', ipAddress, userAgent }, ec).catch(() => {});\n this.logDebug('TOTP setup verified successfully', { userId });\n\n return { verified: true };\n }\n}\n"]}
1
+ {"version":3,"file":"auth-mfa.backend.service.js","sourceRoot":"","sources":["../../../../../../../src/security/authentications/services/auth-methods/auth-mfa.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport crypto from 'crypto';\nimport { injectable, inject } from 'inversify';\nimport { TOTP, Secret } from 'otpauth';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport { AuthUserLookupBackendService } from '~wildo-backend-lib/security/authentications/services/utils/auth-user-lookup.backend.service';\nimport { AuthCredentialOpsBackendService } from '~wildo-backend-lib/security/authentications/services/utils/auth-credential-ops.backend.service';\nimport { RedisService } from '~wildo-backend-lib/databases/services/redis.service';\nimport { AUTH_REDIS_KEYS } from '~wildo-backend-lib/security/authentications/models/auth-constants.backend';\nimport { ErrorType, ErrorCustomMessageReference, MFA_BACKUP_CODE_CONTRACT } from '@wildo-ai/saas-models';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\n\nexport interface TotpSetupResult {\n secret: string;\n qrCodeUri: string;\n backupCodes: string[];\n}\n\nexport interface TotpSetupParams {\n userId: string;\n email: string;\n ipAddress: string;\n userAgent: string;\n}\n\nexport interface TotpVerifySetupParams {\n userId: string;\n code: string;\n ipAddress: string;\n userAgent: string;\n}\n\n/**\n * Outcome of {@link AuthMfaBackendService.verifyEnrolledSecondFactor}.\n *\n * `usedBackupCode` is reported rather than folded into `valid` because a consumed backup code is a\n * materially different event from a TOTP success: it is single-use, it depletes a finite reserve,\n * and callers surface it (login returns it as `challengeData`).\n */\nexport interface EnrolledSecondFactorVerification {\n readonly valid: boolean;\n readonly usedBackupCode: boolean;\n /** Set when the principal has no usable enrolled second factor at all (no verified TOTP secret). */\n readonly notEnrolled: boolean;\n}\n\n/**\n * TOTP enrollment — setup and verify-setup.\n * Uses static `otpauth` import (cleanup from dynamic import in controller).\n */\n@injectable()\nexport class AuthMfaBackendService {\n private readonly logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) private readonly appConfigService: AppConfigurationService,\n @inject(SAAS_SERVICE_TYPES.AuditLogsService) private readonly auditLogsService: AuditLogsBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthCredentialOpsService) private readonly credentialOps: AuthCredentialOpsBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthUserLookupService) private readonly userLookup: AuthUserLookupBackendService,\n // Appended LAST on purpose: a constructor parameter inserted in the middle silently re-binds\n // every following injection in this service's DI signature.\n @inject(SAAS_SERVICE_TYPES.RedisService) private readonly redisService: RedisService,\n ) {\n this.logDebug = this.logger.debugByType('AuthMfa');\n this.logger.setDebugTypeLogging('AuthMfa', false);\n }\n\n /**\n * Verify an ALREADY-ENROLLED second factor: a TOTP code, or one of the principal's backup codes.\n *\n * # Why this is a shared primitive rather than per-caller logic\n *\n * Two callers need exactly this question answered — the login second-factor step\n * (`authentication-orchestrator`) and step-up re-authentication\n * (`auth-method-management.reAuthenticate`) — and the answer carries three protections that a\n * naive `totp.validate()` does not:\n *\n * - **Anti-replay (RFC 6238 §5.2).** A TOTP code stays valid for a whole time step plus the\n * configured drift window, so without this a code observed once can be presented again inside\n * that window. The absolute time step is recorded in Redis and anything at or below it is\n * refused. This is precisely what makes step-up worth having: a code shoulder-surfed during\n * login must NOT also authorize a destructive operation moments later.\n * - **Backup-code fallback with constant-time comparison**, iterating every stored hash so a\n * match position cannot leak through timing, and consuming the code on success.\n * - **The enrollment guard** — an unverified or absent secret is `notEnrolled`, distinct from a\n * wrong code, so callers can tell \"you have no second factor\" from \"that code is wrong\".\n *\n * Duplicating any of this per caller would create a second, weaker door onto the same credential.\n * Callers own their own reactions (session attempt counting, audit events, error shapes); this\n * method owns only the cryptographic and replay question and NEVER throws for a bad code.\n *\n * assurance-control: WILDO.IDENTITY.MULTI_FACTOR — one verification authority for enrolled second\n * factors, replay-resistant and timing-safe.\n */\n public async verifyEnrolledSecondFactor(\n ec: ExecutionContext<any>,\n userId: string,\n token: string,\n ): Promise<EnrolledSecondFactorVerification> {\n const cred = await this.credentialOps.readByUserId(ec, userId);\n\n if (!cred || !cred.totpVerified || !cred.secretBase32) {\n return { valid: false, usedBackupCode: false, notEnrolled: true };\n }\n\n const { algorithm, digits, period, window } = this.appConfigService.config.auth.totpOperational;\n const totp = new TOTP({ secret: cred.secretBase32, algorithm, digits, period });\n\n const delta = totp.validate({ token, window });\n let isValidTOTP = delta !== null;\n\n // Anti-replay: store the ABSOLUTE time step (current period + delta), not the relative delta —\n // a relative value stops being comparable the moment the period rolls over.\n if (isValidTOTP) {\n const currentTimeStep = Math.floor(Date.now() / (period * 1000));\n const absoluteStep = currentTimeStep + delta!;\n const replayKey = `${AUTH_REDIS_KEYS.TOTP_USED}${userId}`;\n const lastUsedStep = await this.redisService.get<string>(replayKey);\n if (lastUsedStep !== null && absoluteStep <= parseInt(lastUsedStep as string, 10)) {\n isValidTOTP = false;\n } else {\n await this.redisService.set(replayKey, String(absoluteStep), { ttl: 90 });\n }\n }\n\n if (isValidTOTP) return { valid: true, usedBackupCode: false, notEnrolled: false };\n\n if (cred.backupCodesHashed?.length > 0) {\n const inputHash = crypto.createHash('sha256').update(token).digest('hex');\n const inputBuf = Buffer.from(inputHash, 'hex');\n\n let matchIndex = -1;\n for (let i = 0; i < cred.backupCodesHashed.length; i++) {\n const storedBuf = Buffer.from(cred.backupCodesHashed[i], 'hex');\n if (inputBuf.length === storedBuf.length && crypto.timingSafeEqual(inputBuf, storedBuf)) {\n matchIndex = i;\n // Deliberately NO `break` — always compare every hash so the match position cannot leak.\n }\n }\n\n if (matchIndex >= 0) {\n const updatedCodes = [...cred.backupCodesHashed];\n updatedCodes.splice(matchIndex, 1);\n await this.credentialOps.updateBackupCodes(ec, cred._id, updatedCodes);\n return { valid: true, usedBackupCode: true, notEnrolled: false };\n }\n }\n\n return { valid: false, usedBackupCode: false, notEnrolled: false };\n }\n\n async setupTOTP(ec: ExecutionContext<any>, params: TotpSetupParams): Promise<TotpSetupResult> {\n const { userId, ipAddress, userAgent } = params;\n\n this.logDebug('MFA TOTP setup requested', { userId });\n\n const cred = await this.credentialOps.readByUserId(ec, userId);\n\n if (!cred) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, undefined, {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_USER_CREDENTIALS_NOT_FOUND,\n });\n }\n\n if (cred.totpVerified) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.MFA_TOTP_ALREADY_ENROLLED,\n });\n }\n\n const secret = new Secret();\n const totpConfig = this.appConfigService.config.auth.totpOperational;\n const issuer = totpConfig.issuer || this.appConfigService.config.appIdentity.appName || 'Wildo';\n\n const accountName = await this.userLookup.getUserEmail(ec, userId) || userId;\n const label = totpConfig.includeIssuerInLabel ? accountName : accountName;\n\n const totp = new TOTP({\n issuer,\n label,\n algorithm: totpConfig.algorithm,\n digits: totpConfig.digits,\n period: totpConfig.period,\n secret,\n });\n const qrCodeUri = totp.toString();\n\n const backupCodes: string[] = [];\n const backupCodesHashed: string[] = [];\n // Count and length come from `MFA_BACKUP_CODE_CONTRACT`, which the customer documentation also\n // projects: what a person is told they receive is what this loop issues.\n for (let i = 0; i < MFA_BACKUP_CODE_CONTRACT.codeCount; i++) {\n const code = crypto.randomBytes(MFA_BACKUP_CODE_CONTRACT.codeRandomBytes).toString('hex');\n backupCodes.push(code);\n backupCodesHashed.push(crypto.createHash('sha256').update(code).digest('hex'));\n }\n\n await this.credentialOps.writeTotpSetup(ec, cred._id, { secretBase32: secret.base32, backupCodesHashed });\n\n this.auditLogsService.logUserMFASetupStarted({ userId: userId, mfaMethod: 'totp', ipAddress, userAgent }, ec).catch(() => {});\n this.logDebug('TOTP secret generated on credentials, awaiting verification', { userId });\n\n return { secret: secret.base32, qrCodeUri, backupCodes };\n }\n\n async verifyTOTPSetup(ec: ExecutionContext<any>, params: TotpVerifySetupParams): Promise<{ verified: boolean }> {\n const { userId, code, ipAddress, userAgent } = params;\n\n this.logDebug('MFA TOTP verify-setup requested', { userId });\n\n const cred = await this.credentialOps.readByUserId(ec, userId);\n\n if (!cred || !cred.secretBase32 || cred.totpVerified) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, undefined, {\n customMessageReference: ErrorCustomMessageReference.MFA_TOTP_SETUP_NOT_INITIATED,\n });\n }\n\n const totpConfig = this.appConfigService.config.auth.totpOperational;\n const totp = new TOTP({\n secret: cred.secretBase32,\n algorithm: totpConfig.algorithm,\n digits: totpConfig.digits,\n period: totpConfig.period,\n });\n const delta = totp.validate({ token: code, window: totpConfig.window });\n if (delta === null) {\n throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.MFA_TOTP_CODE_INVALID,\n });\n }\n\n await this.credentialOps.confirmTotpSetup(ec, cred._id);\n\n this.auditLogsService.logUserMFASetupCompleted({ userId: userId, mfaMethod: 'totp', ipAddress, userAgent }, ec).catch(() => {});\n this.logDebug('TOTP setup verified successfully', { userId });\n\n return { verified: true };\n }\n}\n"]}
@@ -4,6 +4,7 @@ import { AppConfigurationService } from '../../../config/services/app-configurat
4
4
  import { ErrorBuilderBackendService } from '../../../errors/services/error-builder.backend.service';
5
5
  import { RedisService } from '../../../databases/services/redis.service';
6
6
  import { JwtBackendService } from '../../../security/jwt/services/jwt.backend.service';
7
+ import { AuditLogsBackendService } from '../../../compliance/audit-trails/services/audit-logs.backend.service';
7
8
  import type { ServicesRegistryHandlerBackendService } from '../../../base-classes/services/services/services-registry-handler.backend.service';
8
9
  import { AuthExecutionContextFactoryBackendService } from '../../../security/authentications/services/auth-execution-context-factory.backend.service';
9
10
  import { AuthenticationSession, SessionType } from '@wildo-ai/saas-models';
@@ -63,8 +64,9 @@ export declare class AuthTokenIssuerBackendService {
63
64
  private readonly redisService;
64
65
  private readonly servicesRegistryHandler;
65
66
  private readonly authEcFactory;
67
+ private readonly auditLogsService;
66
68
  private readonly logDebug;
67
- constructor(logger: LoggerService, appConfigService: AppConfigurationService, jwtService: JwtBackendService, errorBuilder: ErrorBuilderBackendService, redisService: RedisService, servicesRegistryHandler: ServicesRegistryHandlerBackendService, authEcFactory: AuthExecutionContextFactoryBackendService);
69
+ constructor(logger: LoggerService, appConfigService: AppConfigurationService, jwtService: JwtBackendService, errorBuilder: ErrorBuilderBackendService, redisService: RedisService, servicesRegistryHandler: ServicesRegistryHandlerBackendService, authEcFactory: AuthExecutionContextFactoryBackendService, auditLogsService?: AuditLogsBackendService | null);
68
70
  /**
69
71
  * Issue access + refresh tokens for a completed authentication session.
70
72
  */
@@ -154,5 +156,27 @@ export declare class AuthTokenIssuerBackendService {
154
156
  * eviction arbitrary.
155
157
  */
156
158
  private _trackAndEnforceSessionLimit;
159
+ /**
160
+ * Record each retired refresh chain in the audit trail.
161
+ *
162
+ * Until 2026-09-02 the only trace of an eviction was the debug line above — a security-relevant
163
+ * token-lifecycle act with no durable record, which is exactly what `CLAUDE.md`'s
164
+ * assurance-by-construction rule asks for by name. The cap exists to bound the blast radius of
165
+ * undetected token theft, so a burst of evictions for one principal is among the more interesting
166
+ * things the trail can carry, and it was not carrying it.
167
+ *
168
+ * `evictedIssuedAt` is what makes the record worth having: the cap's security property is that the
169
+ * OLDEST chain goes, and a record naming only the victim would prove a session ended without
170
+ * proving the right one did. An unreadable score yields ABSENCE rather than a synthesised instant —
171
+ * a fabricated timestamp would make the first-in-first-out claim unfalsifiable.
172
+ *
173
+ * Feature-DETECTED rather than null-checked, for the reason recorded at the cross-tenant emitter:
174
+ * adding a `log*` method breaks partially-mocked doubles, the property is absent, and the CALL
175
+ * throws synchronously before any `.catch()` can make it fire-and-forget.
176
+ *
177
+ * assurance-control: WILDO.IDENTITY.SESSION_CONCURRENCY_LIMIT — the bound's enforcement is
178
+ * evidenced, naming WHICH chain was retired and when it was issued.
179
+ */
180
+ private recordEvictions;
157
181
  }
158
182
  //# sourceMappingURL=auth-token-issuer.backend.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-token-issuer.backend.service.d.ts","sourceRoot":"","sources":["../../../../../../src/security/authentications/services/auth-token-issuer.backend.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-token-issuer.backend.service.d.ts","sourceRoot":"","sources":["../../../../../../src/security/authentications/services/auth-token-issuer.backend.service.ts"],"names":[],"mappings":"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"}
@@ -12,19 +12,20 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  };
13
13
  import 'reflect-metadata';
14
14
  import crypto from 'crypto';
15
- import { inject, injectable } from 'inversify';
15
+ import { inject, injectable, optional } from 'inversify';
16
16
  import { SAAS_SERVICE_TYPES } from '../../../inject/models/service-types.js';
17
17
  import { LoggerService } from '../../../monitoring/logger/services/logger.service.js';
18
18
  import { AppConfigurationService } from '../../../config/services/app-configuration.backend.service.js';
19
19
  import { ErrorBuilderBackendService } from '../../../errors/services/error-builder.backend.service.js';
20
20
  import { RedisService } from '../../../databases/services/redis.service.js';
21
21
  import { JwtBackendService } from '../../../security/jwt/services/jwt.backend.service.js';
22
+ import { AuditLogsBackendService } from '../../../compliance/audit-trails/services/audit-logs.backend.service.js';
22
23
  import { projectCaughtErrorLogFields } from '../../../errors/utils/error-log-level-classification.backend.utils.js';
23
24
  import { AuthExecutionContextFactoryBackendService } from '../../../security/authentications/services/auth-execution-context-factory.backend.service.js';
24
25
  import { ExecutionContext_ExecutionType, SessionType, ErrorType, ErrorCustomMessageReference, CoreResourceType, UserStatus } from '@wildo-ai/saas-models';
25
26
  import { JwtClaim_App_Auth_UserRequest_Schema, JwtClaim_App_RefreshToken_Schema, } from '../../../security/authentications/models/authentications.backend.schemas.js';
26
27
  import { AUTH_REDIS_KEYS, RefreshTokenBlacklistReason, REFRESH_TOKEN_ROTATION_GRACE_SECONDS } from '../../../security/authentications/models/auth-constants.backend.js';
27
- import { buildSessionOrderingScore, CONCURRENT_SESSION_EVICTION_LUA, resolveEvictedJtiBlacklistSeconds, } from '../../../security/authentications/services/utils/concurrent-session-eviction.backend.utils.js';
28
+ import { buildSessionOrderingScore, CONCURRENT_SESSION_EVICTION_LUA, readIssuedAtMilliseconds, resolveEvictedJtiBlacklistSeconds, } from '../../../security/authentications/services/utils/concurrent-session-eviction.backend.utils.js';
28
29
  import { evaluateUserTokenInvalidationFence } from '../../../security/token-invalidation-fence.backend.utils.js';
29
30
  import { evaluateSessionInvalidationFence } from '../../../security/session-invalidation-fence.backend.utils.js';
30
31
  import { getUserAuthorizationVersion, } from '../../../security/authentications/models/user-authorization-version.backend.utils.js';
@@ -81,8 +82,9 @@ let AuthTokenIssuerBackendService = class AuthTokenIssuerBackendService {
81
82
  redisService;
82
83
  servicesRegistryHandler;
83
84
  authEcFactory;
85
+ auditLogsService;
84
86
  logDebug;
85
- constructor(logger, appConfigService, jwtService, errorBuilder, redisService, servicesRegistryHandler, authEcFactory) {
87
+ constructor(logger, appConfigService, jwtService, errorBuilder, redisService, servicesRegistryHandler, authEcFactory, auditLogsService = null) {
86
88
  this.logger = logger;
87
89
  this.appConfigService = appConfigService;
88
90
  this.jwtService = jwtService;
@@ -90,6 +92,7 @@ let AuthTokenIssuerBackendService = class AuthTokenIssuerBackendService {
90
92
  this.redisService = redisService;
91
93
  this.servicesRegistryHandler = servicesRegistryHandler;
92
94
  this.authEcFactory = authEcFactory;
95
+ this.auditLogsService = auditLogsService;
93
96
  this.logDebug = this.logger.debugByType('AuthTokenIssuer');
94
97
  this.logger.setDebugTypeLogging('AuthTokenIssuer', false);
95
98
  }
@@ -572,6 +575,7 @@ let AuthTokenIssuerBackendService = class AuthTokenIssuerBackendService {
572
575
  evictedCount: evictedPairs.length / 2,
573
576
  maxSessions,
574
577
  });
578
+ this.recordEvictions({ userId, evictedPairs, maxSessions, nowMilliseconds });
575
579
  }
576
580
  }
577
581
  catch (error) {
@@ -582,6 +586,50 @@ let AuthTokenIssuerBackendService = class AuthTokenIssuerBackendService {
582
586
  });
583
587
  }
584
588
  }
589
+ /**
590
+ * Record each retired refresh chain in the audit trail.
591
+ *
592
+ * Until 2026-09-02 the only trace of an eviction was the debug line above — a security-relevant
593
+ * token-lifecycle act with no durable record, which is exactly what `CLAUDE.md`'s
594
+ * assurance-by-construction rule asks for by name. The cap exists to bound the blast radius of
595
+ * undetected token theft, so a burst of evictions for one principal is among the more interesting
596
+ * things the trail can carry, and it was not carrying it.
597
+ *
598
+ * `evictedIssuedAt` is what makes the record worth having: the cap's security property is that the
599
+ * OLDEST chain goes, and a record naming only the victim would prove a session ended without
600
+ * proving the right one did. An unreadable score yields ABSENCE rather than a synthesised instant —
601
+ * a fabricated timestamp would make the first-in-first-out claim unfalsifiable.
602
+ *
603
+ * Feature-DETECTED rather than null-checked, for the reason recorded at the cross-tenant emitter:
604
+ * adding a `log*` method breaks partially-mocked doubles, the property is absent, and the CALL
605
+ * throws synchronously before any `.catch()` can make it fire-and-forget.
606
+ *
607
+ * assurance-control: WILDO.IDENTITY.SESSION_CONCURRENCY_LIMIT — the bound's enforcement is
608
+ * evidenced, naming WHICH chain was retired and when it was issued.
609
+ */
610
+ recordEvictions(input) {
611
+ const auditLogsService = this.auditLogsService;
612
+ if (typeof auditLogsService?.logUserSessionEvicted !== 'function')
613
+ return;
614
+ for (let index = 0; index < input.evictedPairs.length; index += 2) {
615
+ const evictedJti = input.evictedPairs[index];
616
+ if (!evictedJti)
617
+ continue;
618
+ const issuedAtMilliseconds = readIssuedAtMilliseconds(input.evictedPairs[index + 1]);
619
+ try {
620
+ void auditLogsService.logUserSessionEvicted({
621
+ userId: input.userId,
622
+ evictedJti,
623
+ ...(issuedAtMilliseconds === undefined ? {} : { evictedIssuedAt: new Date(issuedAtMilliseconds) }),
624
+ configuredMaximum: input.maxSessions,
625
+ retainedCount: input.maxSessions,
626
+ }).catch(() => undefined);
627
+ }
628
+ catch {
629
+ /* An observer must never fail the issuance it observes. */
630
+ }
631
+ }
632
+ }
585
633
  };
586
634
  AuthTokenIssuerBackendService = __decorate([
587
635
  injectable(),
@@ -592,11 +640,14 @@ AuthTokenIssuerBackendService = __decorate([
592
640
  __param(4, inject(SAAS_SERVICE_TYPES.RedisService)),
593
641
  __param(5, inject(SAAS_SERVICE_TYPES.ServicesRegistryHandlerService)),
594
642
  __param(6, inject(SAAS_SERVICE_TYPES.AuthExecutionContextFactory)),
643
+ __param(7, inject(SAAS_SERVICE_TYPES.AuditLogsService)),
644
+ __param(7, optional()),
595
645
  __metadata("design:paramtypes", [LoggerService,
596
646
  AppConfigurationService,
597
647
  JwtBackendService,
598
648
  ErrorBuilderBackendService,
599
- RedisService, Function, AuthExecutionContextFactoryBackendService])
649
+ RedisService, Function, AuthExecutionContextFactoryBackendService,
650
+ AuditLogsBackendService])
600
651
  ], AuthTokenIssuerBackendService);
601
652
  export { AuthTokenIssuerBackendService };
602
653
  //# sourceMappingURL=auth-token-issuer.backend.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-token-issuer.backend.service.js","sourceRoot":"","sources":["../../../../../../src/security/authentications/services/auth-token-issuer.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport crypto from 'crypto';\nimport { inject, injectable } from 'inversify';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { RedisService } from '~wildo-backend-lib/databases/services/redis.service';\nimport { JwtBackendService } from '~wildo-backend-lib/security/jwt/services/jwt.backend.service';\nimport { projectCaughtErrorLogFields } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport type { ServicesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/services/services-registry-handler.backend.service';\nimport { AuthExecutionContextFactoryBackendService } from '~wildo-backend-lib/security/authentications/services/auth-execution-context-factory.backend.service';\nimport { AuthenticationSession, ExecutionContext_ExecutionType, SessionType, ErrorType, ErrorCustomMessageReference, CoreResourceType, UserStatus } from '@wildo-ai/saas-models';\nimport {\n JwtClaim_App_Auth_UserRequest_Schema,\n JwtClaim_App_RefreshToken_Schema,\n Jwt_RefreshToken_CreationParameter,\n} from '~wildo-backend-lib/security/authentications/models/authentications.backend.schemas';\nimport { AUTH_REDIS_KEYS, RefreshTokenBlacklistReason, REFRESH_TOKEN_ROTATION_GRACE_SECONDS } from '~wildo-backend-lib/security/authentications/models/auth-constants.backend';\nimport {\n buildSessionOrderingScore,\n CONCURRENT_SESSION_EVICTION_LUA,\n resolveEvictedJtiBlacklistSeconds,\n} from '~wildo-backend-lib/security/authentications/services/utils/concurrent-session-eviction.backend.utils';\nimport { evaluateUserTokenInvalidationFence } from '~wildo-backend-lib/security/token-invalidation-fence.backend.utils';\nimport { evaluateSessionInvalidationFence } from '~wildo-backend-lib/security/session-invalidation-fence.backend.utils';\nimport {\n getUserAuthorizationVersion,\n type AuthorizationVersionedUser,\n} from '~wildo-backend-lib/security/authentications/models/user-authorization-version.backend.utils';\n\n/** L3: Default max concurrent refresh tokens per user. */\nconst DEFAULT_MAX_CONCURRENT_SESSIONS = 10;\n\n/**\n * Result of token issuance.\n */\nexport interface AuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresIn?: number;\n}\n\n/**\n * A-9 — the machine-readable signal a refresh-token REUSE rejection carries so a CALLER can audit the\n * theft. This issuer performs the family-wide revocation itself but deliberately does NOT audit\n * (see the class JSDoc — TRAP 13: callers decide what to audit); instead it TAGS its thrown rejection\n * with this signal, and each refresh caller reads it via {@link extractRefreshTokenReuseSignal} and\n * emits `USER_REFRESH_TOKEN_REUSE_DETECTED`. Never carries the raw token — only the reused `jti`.\n */\nexport interface RefreshTokenReuseSignal {\n /** The user whose whole token family was revoked (the victim). */\n userId: string;\n /** The reused refresh token's `jti` (the replayed credential id). */\n reusedJti: string;\n /** The session the reused token belonged to, when present on the claim. */\n sessionId?: string;\n /** Whether the `TOKEN_INVALID_BEFORE` family-revoke was written (the action the issuer took). */\n familyRevoked: boolean;\n}\n\n/**\n * Symbol-keyed so the signal rides the thrown rejection object WITHOUT ever appearing in a string-key\n * serialization or the error sanitizer (the reuse detail is internal, for the auditing caller only).\n * `Symbol.for` (the global registry, not a per-module `Symbol()`) so tag ⇄ extract still match if this\n * module is loaded under both ESM and CJS.\n */\nconst REFRESH_TOKEN_REUSE_SIGNAL_KEY = Symbol.for('wildo.auth.refreshTokenReuseSignal');\n\n/**\n * Tag a refresh-rejection error with the reuse signal (A-9). The issuer calls this after performing the\n * family revoke; the CALLER reads it back and emits the audit — preserving the issuer's no-audit boundary.\n */\nexport function tagRefreshTokenReuseSignal<E extends object>(error: E, signal: RefreshTokenReuseSignal): E {\n Object.defineProperty(error, REFRESH_TOKEN_REUSE_SIGNAL_KEY, {\n value: signal,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n return error;\n}\n\n/**\n * Read the refresh-reuse signal off a caught error, or `null` when it is not a reuse rejection (a plain\n * revoked/expired/invalid refresh). A caller uses this to emit `USER_REFRESH_TOKEN_REUSE_DETECTED` for\n * the theft case ONLY, then map the error to its own rejection.\n */\nexport function extractRefreshTokenReuseSignal(error: unknown): RefreshTokenReuseSignal | null {\n if (error !== null && typeof error === 'object' && REFRESH_TOKEN_REUSE_SIGNAL_KEY in error) {\n return (error as Record<typeof REFRESH_TOKEN_REUSE_SIGNAL_KEY, RefreshTokenReuseSignal>)[REFRESH_TOKEN_REUSE_SIGNAL_KEY];\n }\n return null;\n}\n\n/**\n * AuthTokenIssuerBackendService\n *\n * Responsibilities:\n * - Issue access tokens (delegates to JwtBackendService)\n * - Issue refresh tokens (delegates to JwtBackendService)\n *\n * This service does NOT:\n * - Manage sessions (that's AuthSessionStore)\n * - Verify credentials (that's AuthCredentialVerifier)\n * - Audit log (TRAP 13: callers decide what to audit based on full context)\n */\n@injectable()\nexport class AuthTokenIssuerBackendService {\n private readonly logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) private readonly appConfigService: AppConfigurationService,\n @inject(SAAS_SERVICE_TYPES.JwtBackendService) private readonly jwtService: JwtBackendService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.RedisService) private readonly redisService: RedisService,\n @inject(SAAS_SERVICE_TYPES.ServicesRegistryHandlerService) private readonly servicesRegistryHandler: ServicesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthExecutionContextFactory) private readonly authEcFactory: AuthExecutionContextFactoryBackendService,\n ) {\n this.logDebug = this.logger.debugByType('AuthTokenIssuer');\n this.logger.setDebugTypeLogging('AuthTokenIssuer', false);\n }\n\n /**\n * Issue access + refresh tokens for a completed authentication session.\n */\n public async issueTokens(\n session: AuthenticationSession,\n options: { includeRefreshToken?: boolean; lastAuthenticatedAt?: number; activeUserType?: string } = {},\n ): Promise<AuthTokens> {\n const userId = session.userId;\n\n if (!userId) {\n throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_USER_CREDENTIALS_NOT_FOUND,\n context: { missingField: 'userId' },\n });\n }\n\n return this._issueTokensForUser(userId, session.sessionId, options.includeRefreshToken ?? true, {\n lastAuthenticatedAt: options.lastAuthenticatedAt,\n activeUserType: options.activeUserType ?? session.activeUserType ?? undefined,\n });\n }\n\n /**\n * Issue tokens for an user directly (without a full session).\n * Used by OAuth2/SAML/registration callback flows (TRAP 12) which bypass the session state machine.\n * `activeUserType` is resolved internally from the user record when not provided.\n */\n public async issueTokensForUser(\n userId: string,\n sessionId?: string,\n stepUpClaims?: {\n lastAuthenticatedAt?: number;\n activeUserType?: string;\n sessionType?: SessionType;\n },\n options?: {\n skipSessionTracking?: boolean;\n refreshTokenExpiresAt?: Date;\n },\n ): Promise<AuthTokens> {\n return this._issueTokensForUser(userId, sessionId, true, stepUpClaims, options);\n }\n\n /**\n * Internal implementation for token issuance.\n * Resolves `activeUserType` from the user DB record when not provided by the caller (N-2).\n */\n private async _issueTokensForUser(\n userId: string,\n sessionId?: string,\n includeRefreshToken: boolean = true,\n stepUpClaims?: {\n lastAuthenticatedAt?: number;\n activeUserType?: string;\n sessionType?: SessionType;\n },\n options?: {\n skipSessionTracking?: boolean;\n refreshTokenExpiresAt?: Date;\n /**\n * Refresh rotation passes the predecessor's durable authorization\n * version. Rechecking it on the issuance read closes the gap between the\n * refresh preflight read and successor signing.\n */\n expectedAuthorizationVersion?: number;\n },\n ): Promise<AuthTokens> {\n let emailVerified: boolean | undefined;\n let resolvedActiveUserType = stepUpClaims?.activeUserType;\n\n // Token issuance is a security decision, so the User read and its durable\n // version are mandatory. A best-effort read here would mint an unfenced\n // token precisely when the database boundary is unavailable.\n const tokenEc = await this.authEcFactory.createForAuthenticatedAuthOperation(\n userId,\n this.authEcFactory.getUsersReadOperationPath(),\n );\n const user =\n await this.servicesRegistryHandler.read<AuthorizationVersionedUser>(\n CoreResourceType.USERS,\n tokenEc,\n { _id: userId },\n );\n const authorizationVersion = getUserAuthorizationVersion(user);\n if (!user || authorizationVersion === null) {\n // Two very different faults, deliberately reported apart. Collapsing them into one reason\n // string sent a production investigation looking for a missing version field when the row\n // itself may simply not have been readable — the caller-facing answer is identical either way\n // (fail closed), but the operator-facing one is not: \"no such row\" points at the read's scope\n // or at write visibility, \"no version\" points at the persistence contract.\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n context: {\n reason: user\n ? 'user_authorization_version_unreadable'\n : 'user_row_not_readable_for_issuance',\n userId,\n // Only ever the SHAPE of what came back — never the value, which is an authorization\n // fence and does not belong in a log line.\n authorizationVersionType: typeof (user as AuthorizationVersionedUser | null)?._version,\n userStatus: user?.status,\n },\n },\n );\n }\n if (\n options?.expectedAuthorizationVersion !== undefined\n && authorizationVersion !== options.expectedAuthorizationVersion\n ) {\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n context: {\n reason: 'user_authorization_version_changed_during_issuance',\n userId,\n },\n },\n );\n }\n\n if (user.status === UserStatus.ACTIVE) {\n emailVerified = true;\n } else if (user.status === UserStatus.PENDING_VERIFICATION) {\n emailVerified = false;\n }\n\n // A caller-carried active type is valid only while the live User still\n // owns it. This prevents direct issuance from preserving a removed auth\n // policy even outside refresh rotation.\n if (resolvedActiveUserType && !user.userTypes?.includes(resolvedActiveUserType)\n ) {\n resolvedActiveUserType = undefined;\n }\n if (!resolvedActiveUserType && user.userTypes?.length) {\n resolvedActiveUserType = user.userTypes[0];\n }\n\n const lastAuthenticatedAt = stepUpClaims?.lastAuthenticatedAt;\n\n const accessClaim = JwtClaim_App_Auth_UserRequest_Schema.parse({\n type: ExecutionContext_ExecutionType.USER_REQUEST,\n userId,\n authorizationVersion,\n sessionType: stepUpClaims?.sessionType ?? SessionType.PERSISTENT,\n ...(emailVerified !== undefined ? { emailVerified } : {}),\n ...(lastAuthenticatedAt !== undefined ? { lastAuthenticatedAt } : {}),\n ...(resolvedActiveUserType ? { activeUserType: resolvedActiveUserType } : {}),\n // revocation-C: carry the session id onto the ACCESS claim (it was already on the refresh claim), so the\n // access-token guard can enforce a per-session revocation fence and the OIDC id_token can carry `sid`.\n ...(sessionId ? { sessionId } : {}),\n });\n\n const accessToken = await this.jwtService.createJwtForAppAuth(accessClaim);\n // Resolved AppConfiguration always has this field (required on\n // `AppConfiguration_JWT_Schema`); operator-authored value flows from\n // `wildo.infra.<env>.config.ts` `serviceAuth.accessTokenExpirationMinutes`\n // through apps-manager bootstrap material and is overlaid by\n // `app-configuration-loader.backend.service.ts`. No literal fallback\n // here on purpose — F-24 (Slice 3.3) made `serviceAuth` the single\n // source of truth, so a missing value should fail the boot rather than\n // silently downgrade to a 15-minute floor.\n const accessTokenMinutes = this.appConfigService.config.jwt.accessTokenExpirationMinutes;\n const accessTokenExpiresInSeconds = accessTokenMinutes * 60;\n this.logDebug('Access token issued', { userId, expiresIn: accessTokenExpiresInSeconds });\n\n let refreshToken: string | undefined;\n if (includeRefreshToken) {\n const jti = crypto.randomUUID();\n const refreshClaim: Jwt_RefreshToken_CreationParameter = {\n type: ExecutionContext_ExecutionType.USER_REQUEST,\n userId,\n authorizationVersion,\n jti,\n sessionId: sessionId,\n sessionType: stepUpClaims?.sessionType ?? SessionType.PERSISTENT,\n ...(lastAuthenticatedAt !== undefined ? { lastAuthenticatedAt } : {}),\n ...(resolvedActiveUserType ? { activeUserType: resolvedActiveUserType } : {}),\n ...(options?.refreshTokenExpiresAt ? { refreshTokenExpiresAt: Math.floor(options.refreshTokenExpiresAt.getTime() / 1000) } : {}),\n };\n\n refreshToken = await this.jwtService.createRefreshToken(\n refreshClaim,\n options?.refreshTokenExpiresAt\n ? { expiresInSeconds: Math.max(1, Math.floor((options.refreshTokenExpiresAt.getTime() - Date.now()) / 1000)) }\n : undefined,\n );\n\n if (!options?.skipSessionTracking) {\n await this._trackAndEnforceSessionLimit(userId, jti, resolvedActiveUserType);\n }\n\n this.logDebug('Refresh token issued', { userId, sessionType: stepUpClaims?.sessionType });\n }\n\n return {\n accessToken,\n refreshToken,\n expiresIn: accessTokenExpiresInSeconds,\n };\n }\n\n /**\n * Refresh an access token using a valid refresh token.\n *\n * Flow:\n * 1. Verify the refresh token JWT signature + expiration\n * 2. Parse claims using JwtClaim_App_RefreshToken_Schema (discriminated union)\n * 3. Issue a fresh access token for the same user\n * 4. Optionally rotate the refresh token (always rotate for security)\n *\n * @param refreshToken - The JWT refresh token string\n * @returns Fresh access + refresh tokens\n * @throws AUTHENTICATION error if the refresh token is invalid/expired\n */\n public async refreshAccessToken(refreshToken: string): Promise<AuthTokens> {\n // Step 1: Verify the refresh token JWT (signature + expiry + audience=REFRESH_TOKEN)\n let decoded: unknown;\n try {\n decoded = await this.jwtService.verifyRefreshToken(refreshToken);\n } catch (error) {\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // Step 2: Parse claims using the discriminated union schema\n const parseResult = JwtClaim_App_RefreshToken_Schema.safeParse(decoded);\n if (!parseResult.success) {\n this.logger.warn('Refresh token claims failed validation', { issues: parseResult.error.issues });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n const claims = parseResult.data;\n\n // Step 2b: Read the live User and enforce the durable authorization fence\n // BEFORE the rotation-grace early return. A cached successor must not be\n // returned after userTypes/appRoles/status changed.\n const ec = await this.authEcFactory.createForAuthenticatedAuthOperation(\n claims.userId,\n this.authEcFactory.getUsersReadOperationPath(),\n );\n const currentUser =\n await this.servicesRegistryHandler.read<AuthorizationVersionedUser>(\n CoreResourceType.USERS,\n ec,\n { _id: claims.userId },\n );\n const currentAuthorizationVersion = getUserAuthorizationVersion(currentUser);\n if (!currentUser || currentUser.status !== UserStatus.ACTIVE) {\n this.logger.warn('Refresh token for non-active user', {\n jti: claims.jti,\n userId: claims.userId,\n userStatus: currentUser?.status ?? 'NOT_FOUND',\n });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_ACCOUNT_LOCKED,\n context: {\n userStatus: currentUser?.status ?? 'NOT_FOUND',\n source: 'token_refresh',\n },\n },\n );\n }\n if (\n currentAuthorizationVersion === null\n || currentAuthorizationVersion !== claims.authorizationVersion\n ) {\n this.logger.warn(\n 'Refresh token authorization version no longer matches User',\n {\n jti: claims.jti,\n userId: claims.userId,\n tokenAuthorizationVersion: claims.authorizationVersion,\n currentAuthorizationVersion,\n },\n );\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n context: { reason: 'user_authorization_version_changed' },\n },\n );\n }\n\n // Keep explicit all-session invalidation (password reset / theft\n // detection) ahead of rotation grace as well. AuthorizationVersion closes\n // database authorization races; this timestamp remains the independent\n // credential/session revocation mechanism.\n // GRACE-BOUNDARY (fixed here 2026-07-29, moved to the shared owner 2026-08-01). The fail-closed\n // `<=` — and the full rationale for why the same second must be rejected, and why raising timestamp\n // precision alone would NOT fix it — now lives in {@link evaluateUserTokenInvalidationFence}, which\n // every auth front shares. It was six hand-written copies of this comparison that let the fix land on\n // two of them while four kept the bypassing `<`. Refresh-specific note: this fence is checked BEFORE\n // rotation grace, so a stolen chain cannot ride the grace window past an invalidation.\n const { isRevoked, invalidBefore } = await evaluateUserTokenInvalidationFence(\n this.redisService, claims.userId, claims.iat,\n );\n if (isRevoked) {\n this.logger.warn('Refresh token issued before user-level invalidation', {\n jti: claims.jti,\n userId: claims.userId,\n tokenIat: claims.iat,\n invalidBefore,\n });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // revocation-C: PER-SESSION fence. Distinct from the user-wide fence above — a single-session\n // terminate (OIDC end_session / per-device logout) evicts THIS session's refresh chain while the\n // user's other sessions stay live. Checked here so the terminated session cannot rotate a new\n // access token; the access guard checks the same fence so the current access token also dies.\n const sessionFence = await evaluateSessionInvalidationFence(\n this.redisService, claims.sessionId, claims.iat,\n );\n if (sessionFence.isRevoked) {\n this.logger.warn('Refresh token issued before per-session invalidation', {\n jti: claims.jti,\n userId: claims.userId,\n sessionId: claims.sessionId,\n tokenIat: claims.iat,\n invalidBefore: sessionFence.invalidBefore,\n });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // Step 2c: Check JTI blacklist (revoked tokens) with reuse detection.\n const blacklistValue = await this.redisService.get<string>(`${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${claims.jti}`);\n if (blacklistValue) {\n if (blacklistValue === RefreshTokenBlacklistReason.ROTATED) {\n // IDEMPOTENT-ROTATION GRACE (checked BEFORE theft revocation): a token\n // re-presented within {@link REFRESH_TOKEN_ROTATION_GRACE_SECONDS} of\n // its rotation is almost always a benign race — a lost/retried refresh\n // response, or a second browsing context that read the shared\n // `__wildo_refresh` cookie a beat before the rotation replaced it. In\n // that case the original rotation cached its successor tokens; return\n // that SAME successor so the racing client converges on one token\n // family instead of being force-logged-out. Genuine theft re-presents\n // the token long after this window, by which time the grace key has\n // expired and control falls through to full revocation below.\n const graceSuccessor = await this.redisService.get<AuthTokens>(\n `${AUTH_REDIS_KEYS.REFRESH_GRACE_SUCCESSOR}${claims.jti}`,\n );\n if (graceSuccessor) {\n // Both durable authorizationVersion and explicit Redis invalidation\n // were checked above. Returning the exact cached successor therefore\n // grants nothing beyond the legitimate rotation winner and cannot\n // bypass an intervening User authorization mutation.\n this.logger.info('Refresh-token reuse within rotation grace window — returning cached successor (benign race, NOT revoking)', {\n jti: claims.jti,\n userId: claims.userId,\n type: claims.type,\n });\n return graceSuccessor;\n }\n\n // REUSE DETECTED outside the grace window: the old refresh token may\n // have been stolen — revoke ALL tokens for this user.\n this.logger.warn('REFRESH TOKEN REUSE DETECTED — possible token theft, revoking all tokens for user', {\n jti: claims.jti,\n userId : claims.userId,\n type: claims.type,\n });\n // F-24 (Slice 3.3): refresh-token TTL is required on the resolved\n // AppConfiguration and authoritatively sourced from operator-authored\n // `serviceAuth.refreshTokenExpirationDays`; no literal fallback.\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.TOKEN_INVALID_BEFORE}${claims.userId}`,\n Math.floor(Date.now() / 1000),\n { ttl: refreshTokenDays * 86400 },\n );\n // A-9: this issuer performed the family revoke but does NOT audit (TRAP 13 — see class JSDoc).\n // TAG the rejection with the reuse signal so the CALLER emits `USER_REFRESH_TOKEN_REUSE_DETECTED`\n // for this theft (the plain-revoked `else` below throws the UNtagged twin, so a routine revoked\n // token is not mis-audited as theft). Distinct throw here — never falls through to the shared one.\n // assurance-control: WILDO.IDENTITY.CREDENTIAL_LIFECYCLE — credential-theft detection + response.\n throw tagRefreshTokenReuseSignal(\n this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN },\n ) as object,\n {\n userId: claims.userId,\n reusedJti: claims.jti,\n sessionId: claims.sessionId,\n familyRevoked: true,\n },\n );\n } else {\n this.logger.warn('Refresh token has been revoked', { jti: claims.jti });\n }\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // Step 3: Issue new tokens — carry forward session-scoped claims, but\n // require the issuance read to observe this same authorization version.\n this.logDebug('Refresh token validated, issuing new tokens', { userId : claims.userId, jti: claims.jti });\n const refreshLastAuthAt = claims.lastAuthenticatedAt;\n const refreshActiveUserType = claims.activeUserType;\n const refreshSessionType = claims.sessionType;\n const refreshTokenExpiresAtClaim = claims.refreshTokenExpiresAt;\n\n const newTokens = await this._issueTokensForUser(\n claims.userId,\n claims.sessionId,\n true,\n {\n lastAuthenticatedAt: refreshLastAuthAt,\n activeUserType: refreshActiveUserType,\n sessionType: refreshSessionType ?? SessionType.PERSISTENT,\n },\n {\n ...(refreshSessionType === SessionType.ONE_OFF ? { skipSessionTracking: true } : {}),\n ...(refreshTokenExpiresAtClaim ? { refreshTokenExpiresAt: new Date(refreshTokenExpiresAtClaim * 1000) } : {}),\n expectedAuthorizationVersion: claims.authorizationVersion,\n },\n );\n\n // Step 4: Blacklist the consumed refresh token JTI as 'rotated'\n const nowSeconds = Math.floor(Date.now() / 1000);\n const ttl = claims.exp ? claims.exp - nowSeconds : 86400;\n if (ttl > 0 && claims.jti) {\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${claims.jti}`,\n RefreshTokenBlacklistReason.ROTATED,\n { ttl },\n );\n this.logDebug('Old refresh token JTI blacklisted after rotation', { jti: claims.jti, ttl });\n\n // Cache the successor under the consumed JTI for the idempotent-rotation\n // grace window so a benign reuse race returns THIS exact successor (see\n // Step 2b) instead of being revoked. The TTL is the grace window clamped\n // to the consumed token's own remaining lifetime — the grace entry is\n // ALWAYS shorter-lived than the blacklist entry above, so once it expires\n // a re-presented token still trips full revocation (theft is preserved).\n //\n // AT-REST EXPOSURE (accepted, bounded): this is the ONLY place the auth\n // subsystem writes live token material to Redis — `REFRESH_BLACKLIST`\n // holds only reason strings, `TOKEN_INVALID_BEFORE` timestamps,\n // `ACTIVE_REFRESH_JTIS` only JTIs, and the session store holds session\n // state, never tokens. We store the FULL successor (incl. the refresh\n // token) because the idempotent guarantee requires returning the exact\n // same tokens — re-issuing would mint a divergent family, and an\n // access-token-only cache would break the lost-rotation-response client\n // (its cookie is still the now-blacklisted predecessor, so it needs the\n // successor refresh token re-sent via `Set-Cookie` to advance). An\n // attacker with Redis read access (snapshot/AOF dump, MONITOR, leaked\n // backup) could therefore harvest refresh tokens minted in the last\n // `graceTtl` seconds — but such an attacker already reads the session\n // store, so the marginal blast radius is one short window. If that window\n // ever needs hardening, encrypt this blob at rest (the read site already\n // fails closed to revocation on a parse miss). It is NOT logged.\n const graceTtl = Math.min(REFRESH_TOKEN_ROTATION_GRACE_SECONDS, ttl);\n if (graceTtl > 0) {\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.REFRESH_GRACE_SUCCESSOR}${claims.jti}`,\n newTokens,\n { ttl: graceTtl },\n );\n this.logDebug('Cached rotation successor for idempotent-grace window', { jti: claims.jti, graceTtl });\n }\n }\n\n return newTokens;\n }\n\n /**\n * Revoke a refresh token by adding its JTI to the Redis blacklist.\n * TTL matches the token's remaining lifetime so the entry auto-expires.\n *\n * Called from the logout route handler.\n */\n public async revokeRefreshToken(refreshToken: string): Promise<void> {\n // Decode the token to extract JTI and exp. We verify the signature\n // so that arbitrary strings can't pollute the blacklist.\n let decoded: unknown;\n try {\n decoded = await this.jwtService.verifyRefreshToken(refreshToken);\n } catch {\n // Token is already invalid/expired — nothing to revoke\n this.logDebug('Refresh token revocation skipped — token is invalid or expired');\n return;\n }\n\n const parseResult = JwtClaim_App_RefreshToken_Schema.safeParse(decoded);\n if (!parseResult.success) {\n this.logDebug('Refresh token revocation skipped — claims are malformed');\n return;\n }\n\n const { jti, exp } = parseResult.data;\n if (!jti) {\n this.logDebug('Refresh token revocation skipped — no JTI');\n return;\n }\n\n // TTL = remaining seconds until token naturally expires\n const nowSeconds = Math.floor(Date.now() / 1000);\n const ttl = exp ? (exp as number) - nowSeconds : 86400; // fallback 24h\n if (ttl <= 0) {\n this.logDebug('Refresh token already expired — no blacklist needed', { jti });\n return;\n }\n\n await this.redisService.set(`${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${jti}`, RefreshTokenBlacklistReason.REVOKED, { ttl });\n\n // L3: Remove from active refresh tokens set\n const claims = parseResult.data;\n const userId = claims.userId;\n try {\n const setKey = this.redisService.buildKey(`${AUTH_REDIS_KEYS.ACTIVE_REFRESH_JTIS}${userId}`);\n await this.redisService.eval('redis.call(\"ZREM\", KEYS[1], ARGV[1]); return 1', [setKey], [jti]);\n } catch {\n // Non-fatal\n }\n\n this.logDebug('Refresh token revoked', { jti, ttlSeconds: ttl });\n }\n\n /**\n * Invalidate all refresh tokens for an user by setting a per-user\n * \"invalid before\" timestamp in Redis. All refresh tokens with `iat` before\n * this timestamp will be rejected on next use.\n *\n * Used on password change/reset (C4) and token reuse detection (H3).\n * TTL matches operator-authored `serviceAuth.refreshTokenExpirationDays`\n * (F-24, Slice 3.3); no literal fallback.\n */\n public async invalidateAllTokensForUser(userId: string): Promise<void> {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.TOKEN_INVALID_BEFORE}${userId}`,\n nowSeconds,\n { ttl: refreshTokenDays * 86400 },\n );\n this.logDebug('All tokens invalidated for user', { userId, invalidBefore: nowSeconds });\n }\n\n /**\n * revocation-C: invalidate a SINGLE login session by setting a per-session \"invalid before\"\n * timestamp in Redis. Every access/refresh token whose `sessionId` matches AND whose `iat` is at or\n * before this timestamp is rejected on next use — at every first-party auth front: the refresh exchange\n * ({@link refreshAccessToken}), the first-party access guard (`auth-jwt-guard.extractAuthenticatedUser`),\n * and the manual-controller / resource-API / socket-handshake fronts (`execution-context-creator`), all\n * via {@link evaluateSessionInvalidationFence}.\n *\n * This is the least-privilege sibling of {@link invalidateAllTokensForUser}: it terminates ONE\n * session (the OIDC `end_session_endpoint` / RP-Initiated Logout, and per-device logout), leaving the\n * user's other sessions live. TTL matches operator-authored `jwt.refreshTokenExpirationDays` — the\n * fence only needs to outlive the longest-lived token that could still carry this `sessionId`, after\n * which no such token can exist and the key is dead weight.\n */\n public async invalidateSession(sessionId: string): Promise<void> {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.SESSION_INVALID_BEFORE}${sessionId}`,\n nowSeconds,\n { ttl: refreshTokenDays * 86400 },\n );\n this.logDebug('Session invalidated (per-session fence set)', { sessionId, invalidBefore: nowSeconds });\n }\n\n /**\n * L3: Track an active refresh token JTI for an user and enforce the concurrent session limit.\n *\n * Uses a Redis sorted set per user where:\n * - Members = JTI strings\n * - Scores = issuedAt in MILLISECONDS (for FIFO eviction)\n *\n * When the set exceeds the max size (default 10), the oldest JTI(s) are evicted\n * and blacklisted so they can't be used anymore.\n *\n * The ordering, the score's unit, and the blacklist TTL all live in\n * `concurrent-session-eviction.backend.utils` — retiring the OLDEST chain is the security property\n * the cap exists to provide, so it is a named, separately-tested policy rather than a detail of\n * this method. See {@link buildSessionOrderingScore} for why whole-second scores silently made the\n * eviction arbitrary.\n */\n private async _trackAndEnforceSessionLimit(userId: string, newJti: string, activeUserType?: string): Promise<void> {\n const userTypeConfig = activeUserType\n ? this.appConfigService.config.userTypes?.[activeUserType]\n : undefined;\n const configuredMax = userTypeConfig?.auth?.maxConcurrentSessions;\n const maxSessions = (configuredMax && configuredMax > 0) ? configuredMax : DEFAULT_MAX_CONCURRENT_SESSIONS;\n // F-24 (Slice 3.3): operator-authored TTL; no literal fallback.\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n const setTtl = refreshTokenDays * 86400;\n const nowMilliseconds = buildSessionOrderingScore();\n const setKey = this.redisService.buildKey(`${AUTH_REDIS_KEYS.ACTIVE_REFRESH_JTIS}${userId}`);\n\n try {\n const evictedPairs = await this.redisService.eval(\n CONCURRENT_SESSION_EVICTION_LUA,\n [setKey],\n [newJti, String(nowMilliseconds), String(maxSessions), String(setTtl)],\n ) as string[] | null;\n\n if (evictedPairs && evictedPairs.length > 0) {\n // evictedPairs is [jti, score, jti, score, ...] — blacklist each with its actual remaining TTL\n for (let i = 0; i < evictedPairs.length; i += 2) {\n const jti = evictedPairs[i];\n const remainingTtl = resolveEvictedJtiBlacklistSeconds({\n rawScore: evictedPairs[i + 1],\n nowMilliseconds,\n refreshTokenTtlSeconds: setTtl,\n });\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${jti}`,\n RefreshTokenBlacklistReason.EVICTED,\n { ttl: remainingTtl },\n );\n }\n this.logDebug('Concurrent session limit enforced — evicted oldest tokens', {\n userId,\n evictedCount: evictedPairs.length / 2,\n maxSessions,\n });\n }\n } catch (error) {\n // Non-fatal: don't block token issuance if tracking fails\n this.logger.warn('Failed to track/enforce concurrent session limit', {\n userId,\n ...projectCaughtErrorLogFields(error),\n });\n }\n }\n}\n"]}
1
+ {"version":3,"file":"auth-token-issuer.backend.service.js","sourceRoot":"","sources":["../../../../../../src/security/authentications/services/auth-token-issuer.backend.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,sEAAsE,CAAC;AAC/G,OAAO,EAAE,0BAA0B,EAAE,MAAM,kEAAkE,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,MAAM,qDAAqD,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,8DAA8D,CAAC;AACjG,OAAO,EAAE,uBAAuB,EAAE,MAAM,gFAAgF,CAAC;AACzH,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAE3H,OAAO,EAAE,yCAAyC,EAAE,MAAM,qGAAqG,CAAC;AAChK,OAAO,EAAyB,8BAA8B,EAAE,WAAW,EAAE,SAAS,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACjL,OAAO,EACL,oCAAoC,EACpC,gCAAgC,GAEjC,MAAM,oFAAoF,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,oCAAoC,EAAE,MAAM,2EAA2E,CAAC;AAC/K,OAAO,EACL,yBAAyB,EACzB,+BAA+B,EAC/B,wBAAwB,EACxB,iCAAiC,GAClC,MAAM,sGAAsG,CAAC;AAC9G,OAAO,EAAE,kCAAkC,EAAE,MAAM,oEAAoE,CAAC;AACxH,OAAO,EAAE,gCAAgC,EAAE,MAAM,sEAAsE,CAAC;AACxH,OAAO,EACL,2BAA2B,GAE5B,MAAM,6FAA6F,CAAC;AAErG,0DAA0D;AAC1D,MAAM,+BAA+B,GAAG,EAAE,CAAC;AA6B3C;;;;;GAKG;AACH,MAAM,8BAA8B,GAAG,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AAExF;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAmB,KAAQ,EAAE,MAA+B;IACpG,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,8BAA8B,EAAE;QAC3D,KAAK,EAAE,MAAM;QACb,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,KAAK;KACpB,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAAC,KAAc;IAC3D,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,8BAA8B,IAAI,KAAK,EAAE,CAAC;QAC3F,OAAQ,KAAgF,CAAC,8BAA8B,CAAC,CAAC;IAC3H,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AAEI,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IAIc;IACU;IACC;IACE;IACP;IACkB;IACH;IAQxD;IAjBF,QAAQ,CAA+D;IAExF,YACsD,MAAqB,EACX,gBAAyC,EACxC,UAA6B,EAC3B,YAAwC,EAC/C,YAA0B,EACR,uBAA8D,EACjE,aAAwD,EAQhH,mBAAmD,IAAI;QAdpB,WAAM,GAAN,MAAM,CAAe;QACX,qBAAgB,GAAhB,gBAAgB,CAAyB;QACxC,eAAU,GAAV,UAAU,CAAmB;QAC3B,iBAAY,GAAZ,YAAY,CAA4B;QAC/C,iBAAY,GAAZ,YAAY,CAAc;QACR,4BAAuB,GAAvB,uBAAuB,CAAuC;QACjE,kBAAa,GAAb,aAAa,CAA2C;QAQhH,qBAAgB,GAAhB,gBAAgB,CAAuC;QAExE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,WAAW,CACtB,OAA8B,EAC9B,UAAoG,EAAE;QAEtG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAE9B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,cAAc,EAAE,SAAS,EAAE;gBACtE,sBAAsB,EAAE,2BAA2B,CAAC,yCAAyC;gBAC7F,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE;aACpC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,mBAAmB,IAAI,IAAI,EAAE;YAC9F,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;YAChD,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,cAAc,IAAI,SAAS;SAC9E,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,kBAAkB,CAC7B,MAAc,EACd,SAAkB,EAClB,YAIC,EACD,OAGC;QAED,OAAO,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAClF,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,mBAAmB,CAC/B,MAAc,EACd,SAAkB,EAClB,sBAA+B,IAAI,EACnC,YAIC,EACD,OASC;QAED,IAAI,aAAkC,CAAC;QACvC,IAAI,sBAAsB,GAAG,YAAY,EAAE,cAAc,CAAC;QAE1D,0EAA0E;QAC1E,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,mCAAmC,CAC1E,MAAM,EACN,IAAI,CAAC,aAAa,CAAC,yBAAyB,EAAE,CAC/C,CAAC;QACF,MAAM,IAAI,GACR,MAAM,IAAI,CAAC,uBAAuB,CAAC,IAAI,CACrC,gBAAgB,CAAC,KAAK,EACtB,OAAO,EACP,EAAE,GAAG,EAAE,MAAM,EAAE,CAChB,CAAC;QACJ,MAAM,oBAAoB,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,IAAI,oBAAoB,KAAK,IAAI,EAAE,CAAC;YAC3C,0FAA0F;YAC1F,0FAA0F;YAC1F,8FAA8F;YAC9F,8FAA8F;YAC9F,2EAA2E;YAC3E,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EACpB,2BAA2B,CAAC,4BAA4B;gBAC1D,OAAO,EAAE;oBACP,MAAM,EAAE,IAAI;wBACV,CAAC,CAAC,uCAAuC;wBACzC,CAAC,CAAC,oCAAoC;oBACxC,MAAM;oBACN,qFAAqF;oBACrF,2CAA2C;oBAC3C,wBAAwB,EAAE,OAAQ,IAA0C,EAAE,QAAQ;oBACtF,UAAU,EAAE,IAAI,EAAE,MAAM;iBACzB;aACF,CACF,CAAC;QACJ,CAAC;QACD,IACE,OAAO,EAAE,4BAA4B,KAAK,SAAS;eAChD,oBAAoB,KAAK,OAAO,CAAC,4BAA4B,EAChE,CAAC;YACD,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EACpB,2BAA2B,CAAC,4BAA4B;gBAC1D,OAAO,EAAE;oBACP,MAAM,EAAE,oDAAoD;oBAC5D,MAAM;iBACP;aACF,CACF,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;YACtC,aAAa,GAAG,IAAI,CAAC;QACvB,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,oBAAoB,EAAE,CAAC;YAC3D,aAAa,GAAG,KAAK,CAAC;QACxB,CAAC;QAED,uEAAuE;QACvE,wEAAwE;QACxE,wCAAwC;QACxC,IAAI,sBAAsB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EAC7E,CAAC;YACD,sBAAsB,GAAG,SAAS,CAAC;QACrC,CAAC;QACD,IAAI,CAAC,sBAAsB,IAAI,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACtD,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,mBAAmB,GAAG,YAAY,EAAE,mBAAmB,CAAC;QAE9D,MAAM,WAAW,GAAG,oCAAoC,CAAC,KAAK,CAAC;YAC7D,IAAI,EAAE,8BAA8B,CAAC,YAAY;YACjD,MAAM;YACN,oBAAoB;YACpB,WAAW,EAAE,YAAY,EAAE,WAAW,IAAI,WAAW,CAAC,UAAU;YAChE,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,mBAAmB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7E,yGAAyG;YACzG,uGAAuG;YACvG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAC3E,+DAA+D;QAC/D,qEAAqE;QACrE,2EAA2E;QAC3E,6DAA6D;QAC7D,qEAAqE;QACrE,mEAAmE;QACnE,uEAAuE;QACvE,2CAA2C;QAC3C,MAAM,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC;QACzF,MAAM,2BAA2B,GAAG,kBAAkB,GAAG,EAAE,CAAC;QAC5D,IAAI,CAAC,QAAQ,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC,CAAC;QAEzF,IAAI,YAAgC,CAAC;QACrC,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAChC,MAAM,YAAY,GAAuC;gBACvD,IAAI,EAAE,8BAA8B,CAAC,YAAY;gBACjD,MAAM;gBACN,oBAAoB;gBACpB,GAAG;gBACH,SAAS,EAAE,SAAS;gBACpB,WAAW,EAAE,YAAY,EAAE,WAAW,IAAI,WAAW,CAAC,UAAU;gBAChE,GAAG,CAAC,mBAAmB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrE,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7E,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjI,CAAC;YAEF,YAAY,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,kBAAkB,CACrD,YAAY,EACZ,OAAO,EAAE,qBAAqB;gBAC5B,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE;gBAC9G,CAAC,CAAC,SAAS,CACd,CAAC;YAEF,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,CAAC;gBAClC,MAAM,IAAI,CAAC,4BAA4B,CAAC,MAAM,EAAE,GAAG,EAAE,sBAAsB,CAAC,CAAC;YAC/E,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAC5F,CAAC;QAED,OAAO;YACL,WAAW;YACX,YAAY;YACZ,SAAS,EAAE,2BAA2B;SACvC,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,KAAK,CAAC,kBAAkB,CAAC,YAAoB;QAClD,qFAAqF;QACrF,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACnE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EAAE,2BAA2B,CAAC,4BAA4B;aACjF,CACF,CAAC;QACJ,CAAC;QAED,4DAA4D;QAC5D,MAAM,WAAW,GAAG,gCAAgC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACxE,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YACjG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EAAE,2BAA2B,CAAC,4BAA4B;aACjF,CACF,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC;QAEhC,0EAA0E;QAC1E,yEAAyE;QACzE,oDAAoD;QACpD,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,mCAAmC,CACrE,MAAM,CAAC,MAAM,EACb,IAAI,CAAC,aAAa,CAAC,yBAAyB,EAAE,CAC/C,CAAC;QACF,MAAM,WAAW,GACf,MAAM,IAAI,CAAC,uBAAuB,CAAC,IAAI,CACrC,gBAAgB,CAAC,KAAK,EACtB,EAAE,EACF,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CACvB,CAAC;QACJ,MAAM,2BAA2B,GAAG,2BAA2B,CAAC,WAAW,CAAC,CAAC;QAC7E,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;YAC7D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,EAAE;gBACpD,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,WAAW;aAC/C,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EACpB,2BAA2B,CAAC,6BAA6B;gBAC3D,OAAO,EAAE;oBACP,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,WAAW;oBAC9C,MAAM,EAAE,eAAe;iBACxB;aACF,CACF,CAAC;QACJ,CAAC;QACD,IACE,2BAA2B,KAAK,IAAI;eACjC,2BAA2B,KAAK,MAAM,CAAC,oBAAoB,EAC9D,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,4DAA4D,EAC5D;gBACE,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,yBAAyB,EAAE,MAAM,CAAC,oBAAoB;gBACtD,2BAA2B;aAC5B,CACF,CAAC;YACF,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EACpB,2BAA2B,CAAC,4BAA4B;gBAC1D,OAAO,EAAE,EAAE,MAAM,EAAE,oCAAoC,EAAE;aAC1D,CACF,CAAC;QACJ,CAAC;QAED,iEAAiE;QACjE,0EAA0E;QAC1E,uEAAuE;QACvE,2CAA2C;QAC3C,gGAAgG;QAChG,oGAAoG;QACpG,oGAAoG;QACpG,sGAAsG;QACtG,qGAAqG;QACrG,uFAAuF;QACvF,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,kCAAkC,CAC3E,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAC7C,CAAC;QACF,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qDAAqD,EAAE;gBACtE,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,GAAG;gBACpB,aAAa;aACd,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EACpB,2BAA2B,CAAC,4BAA4B;aAC3D,CACF,CAAC;QACJ,CAAC;QAED,8FAA8F;QAC9F,iGAAiG;QACjG,8FAA8F;QAC9F,8FAA8F;QAC9F,MAAM,YAAY,GAAG,MAAM,gCAAgC,CACzD,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAChD,CAAC;QACF,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sDAAsD,EAAE;gBACvE,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,QAAQ,EAAE,MAAM,CAAC,GAAG;gBACpB,aAAa,EAAE,YAAY,CAAC,aAAa;aAC1C,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EACpB,2BAA2B,CAAC,4BAA4B;aAC3D,CACF,CAAC;QACJ,CAAC;QAED,sEAAsE;QACtE,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAS,GAAG,eAAe,CAAC,iBAAiB,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QAChH,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,cAAc,KAAK,2BAA2B,CAAC,OAAO,EAAE,CAAC;gBAC3D,uEAAuE;gBACvE,sEAAsE;gBACtE,uEAAuE;gBACvE,8DAA8D;gBAC9D,sEAAsE;gBACtE,sEAAsE;gBACtE,kEAAkE;gBAClE,sEAAsE;gBACtE,oEAAoE;gBACpE,8DAA8D;gBAC9D,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAChD,GAAG,eAAe,CAAC,uBAAuB,GAAG,MAAM,CAAC,GAAG,EAAE,CAC1D,CAAC;gBACF,IAAI,cAAc,EAAE,CAAC;oBACnB,oEAAoE;oBACpE,qEAAqE;oBACrE,kEAAkE;oBAClE,qDAAqD;oBACrD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2GAA2G,EAAE;wBAC5H,GAAG,EAAE,MAAM,CAAC,GAAG;wBACf,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;qBAClB,CAAC,CAAC;oBACH,OAAO,cAAc,CAAC;gBACxB,CAAC;gBAED,qEAAqE;gBACrE,sDAAsD;gBACtD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mFAAmF,EAAE;oBACpG,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,MAAM,EAAG,MAAM,CAAC,MAAM;oBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;iBAClB,CAAC,CAAC;gBACH,kEAAkE;gBAClE,sEAAsE;gBACtE,iEAAiE;gBACjE,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;gBACrF,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CACzB,GAAG,eAAe,CAAC,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,EACzD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAC7B,EAAE,GAAG,EAAE,gBAAgB,GAAG,KAAK,EAAE,CAClC,CAAC;gBACF,+FAA+F;gBAC/F,kGAAkG;gBAClG,gGAAgG;gBAChG,mGAAmG;gBACnG,kGAAkG;gBAClG,MAAM,0BAA0B,CAC9B,IAAI,CAAC,YAAY,CAAC,UAAU,CAC1B,SAAS,CAAC,cAAc,EACxB,SAAS,EACT,EAAE,sBAAsB,EAAE,2BAA2B,CAAC,4BAA4B,EAAE,CAC3E,EACX;oBACE,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,SAAS,EAAE,MAAM,CAAC,GAAG;oBACrB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,aAAa,EAAE,IAAI;iBACpB,CACF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAChC,SAAS,CAAC,cAAc,EACxB,SAAS,EACT;gBACE,sBAAsB,EAAE,2BAA2B,CAAC,4BAA4B;aACjF,CACF,CAAC;QACJ,CAAC;QAED,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,CAAC,QAAQ,CAAC,6CAA6C,EAAE,EAAE,MAAM,EAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1G,MAAM,iBAAiB,GAAG,MAAM,CAAC,mBAAmB,CAAC;QACrD,MAAM,qBAAqB,GAAG,MAAM,CAAC,cAAc,CAAC;QACpD,MAAM,kBAAkB,GAAG,MAAM,CAAC,WAAW,CAAC;QAC9C,MAAM,0BAA0B,GAAG,MAAM,CAAC,qBAAqB,CAAC;QAEhE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAC9C,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,SAAS,EAChB,IAAI,EACJ;YACE,mBAAmB,EAAE,iBAAiB;YACtC,cAAc,EAAE,qBAAqB;YACrC,WAAW,EAAE,kBAAkB,IAAI,WAAW,CAAC,UAAU;SAC1D,EACD;YACE,GAAG,CAAC,kBAAkB,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,IAAI,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7G,4BAA4B,EAAE,MAAM,CAAC,oBAAoB;SAC1D,CACF,CAAC;QAEF,gEAAgE;QAChE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;QACzD,IAAI,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YAC1B,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CACzB,GAAG,eAAe,CAAC,iBAAiB,GAAG,MAAM,CAAC,GAAG,EAAE,EACnD,2BAA2B,CAAC,OAAO,EACnC,EAAE,GAAG,EAAE,CACR,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,kDAAkD,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;YAE5F,yEAAyE;YACzE,wEAAwE;YACxE,yEAAyE;YACzE,sEAAsE;YACtE,0EAA0E;YAC1E,yEAAyE;YACzE,EAAE;YACF,wEAAwE;YACxE,sEAAsE;YACtE,gEAAgE;YAChE,uEAAuE;YACvE,sEAAsE;YACtE,uEAAuE;YACvE,iEAAiE;YACjE,wEAAwE;YACxE,wEAAwE;YACxE,mEAAmE;YACnE,sEAAsE;YACtE,oEAAoE;YACpE,sEAAsE;YACtE,0EAA0E;YAC1E,yEAAyE;YACzE,iEAAiE;YACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;YACrE,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;gBACjB,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CACzB,GAAG,eAAe,CAAC,uBAAuB,GAAG,MAAM,CAAC,GAAG,EAAE,EACzD,SAAS,EACT,EAAE,GAAG,EAAE,QAAQ,EAAE,CAClB,CAAC;gBACF,IAAI,CAAC,QAAQ,CAAC,uDAAuD,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;YACxG,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,kBAAkB,CAAC,YAAoB;QAClD,mEAAmE;QACnE,yDAAyD;QACzD,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACnE,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;YACvD,IAAI,CAAC,QAAQ,CAAC,gEAAgE,CAAC,CAAC;YAChF,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,gCAAgC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACxE,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,CAAC,yDAAyD,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QAED,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC;QACtC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,CAAC,QAAQ,CAAC,2CAA2C,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,wDAAwD;QACxD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAE,GAAc,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,eAAe;QACvE,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,qDAAqD,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9E,OAAO;QACT,CAAC;QAED,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,iBAAiB,GAAG,GAAG,EAAE,EAAE,2BAA2B,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAExH,4CAA4C;QAC5C,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,eAAe,CAAC,mBAAmB,GAAG,MAAM,EAAE,CAAC,CAAC;YAC7F,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,gDAAgD,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAClG,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,uBAAuB,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,0BAA0B,CAAC,MAAc;QACpD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;QACrF,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CACzB,GAAG,eAAe,CAAC,oBAAoB,GAAG,MAAM,EAAE,EAClD,UAAU,EACV,EAAE,GAAG,EAAE,gBAAgB,GAAG,KAAK,EAAE,CAClC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,iCAAiC,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;;;;;;;;;OAaG;IACI,KAAK,CAAC,iBAAiB,CAAC,SAAiB;QAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;QACrF,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CACzB,GAAG,eAAe,CAAC,sBAAsB,GAAG,SAAS,EAAE,EACvD,UAAU,EACV,EAAE,GAAG,EAAE,gBAAgB,GAAG,KAAK,EAAE,CAClC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,6CAA6C,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC,CAAC;IACzG,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACK,KAAK,CAAC,4BAA4B,CAAC,MAAc,EAAE,MAAc,EAAE,cAAuB;QAChG,MAAM,cAAc,GAAG,cAAc;YACnC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,cAAc,CAAC;YAC1D,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,aAAa,GAAG,cAAc,EAAE,IAAI,EAAE,qBAAqB,CAAC;QAClE,MAAM,WAAW,GAAG,CAAC,aAAa,IAAI,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,+BAA+B,CAAC;QAC3G,gEAAgE;QAChE,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;QACrF,MAAM,MAAM,GAAG,gBAAgB,GAAG,KAAK,CAAC;QACxC,MAAM,eAAe,GAAG,yBAAyB,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,eAAe,CAAC,mBAAmB,GAAG,MAAM,EAAE,CAAC,CAAC;QAE7F,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAC/C,+BAA+B,EAC/B,CAAC,MAAM,CAAC,EACR,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CACpD,CAAC;YAErB,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,+FAA+F;gBAC/F,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAChD,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;oBAC5B,MAAM,YAAY,GAAG,iCAAiC,CAAC;wBACrD,QAAQ,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC;wBAC7B,eAAe;wBACf,sBAAsB,EAAE,MAAM;qBAC/B,CAAC,CAAC;oBACH,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CACzB,GAAG,eAAe,CAAC,iBAAiB,GAAG,GAAG,EAAE,EAC5C,2BAA2B,CAAC,OAAO,EACnC,EAAE,GAAG,EAAE,YAAY,EAAE,CACtB,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,QAAQ,CAAC,2DAA2D,EAAE;oBACzE,MAAM;oBACN,YAAY,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC;oBACrC,WAAW;iBACZ,CAAC,CAAC;gBACH,IAAI,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,0DAA0D;YAC1D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kDAAkD,EAAE;gBACnE,MAAM;gBACN,GAAG,2BAA2B,CAAC,KAAK,CAAC;aACtC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACK,eAAe,CAAC,KAKvB;QACC,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAC/C,IAAI,OAAO,gBAAgB,EAAE,qBAAqB,KAAK,UAAU;YAAE,OAAO;QAE1E,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YAClE,MAAM,UAAU,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;YAC7C,IAAI,CAAC,UAAU;gBAAE,SAAS;YAC1B,MAAM,oBAAoB,GAAG,wBAAwB,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YACrF,IAAI,CAAC;gBACH,KAAK,gBAAgB,CAAC,qBAAqB,CAAC;oBAC1C,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,UAAU;oBACV,GAAG,CAAC,oBAAoB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,IAAI,CAAC,oBAAoB,CAAC,EAAE,CAAC;oBAClG,iBAAiB,EAAE,KAAK,CAAC,WAAW;oBACpC,aAAa,EAAE,KAAK,CAAC,WAAW;iBACjC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,2DAA2D;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAA;AA1uBY,6BAA6B;IADzC,UAAU,EAAE;IAKR,WAAA,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACjC,WAAA,MAAM,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAA;IAC3C,WAAA,MAAM,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;IAC5C,WAAA,MAAM,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAA;IAC9C,WAAA,MAAM,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAA;IACvC,WAAA,MAAM,CAAC,kBAAkB,CAAC,8BAA8B,CAAC,CAAA;IACzD,WAAA,MAAM,CAAC,kBAAkB,CAAC,2BAA2B,CAAC,CAAA;IAOtD,WAAA,MAAM,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAA;IAAE,WAAA,QAAQ,EAAE,CAAA;qCAbI,aAAa;QACO,uBAAuB;QAC5B,iBAAiB;QACb,0BAA0B;QACjC,YAAY,YAEI,yCAAyC;QAQ9F,uBAAuB;GAlBjD,6BAA6B,CA0uBzC","sourcesContent":["import 'reflect-metadata';\nimport crypto from 'crypto';\nimport { inject, injectable, optional } from 'inversify';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { RedisService } from '~wildo-backend-lib/databases/services/redis.service';\nimport { JwtBackendService } from '~wildo-backend-lib/security/jwt/services/jwt.backend.service';\nimport { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport { projectCaughtErrorLogFields } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport type { ServicesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/services/services-registry-handler.backend.service';\nimport { AuthExecutionContextFactoryBackendService } from '~wildo-backend-lib/security/authentications/services/auth-execution-context-factory.backend.service';\nimport { AuthenticationSession, ExecutionContext_ExecutionType, SessionType, ErrorType, ErrorCustomMessageReference, CoreResourceType, UserStatus } from '@wildo-ai/saas-models';\nimport {\n JwtClaim_App_Auth_UserRequest_Schema,\n JwtClaim_App_RefreshToken_Schema,\n Jwt_RefreshToken_CreationParameter,\n} from '~wildo-backend-lib/security/authentications/models/authentications.backend.schemas';\nimport { AUTH_REDIS_KEYS, RefreshTokenBlacklistReason, REFRESH_TOKEN_ROTATION_GRACE_SECONDS } from '~wildo-backend-lib/security/authentications/models/auth-constants.backend';\nimport {\n buildSessionOrderingScore,\n CONCURRENT_SESSION_EVICTION_LUA,\n readIssuedAtMilliseconds,\n resolveEvictedJtiBlacklistSeconds,\n} from '~wildo-backend-lib/security/authentications/services/utils/concurrent-session-eviction.backend.utils';\nimport { evaluateUserTokenInvalidationFence } from '~wildo-backend-lib/security/token-invalidation-fence.backend.utils';\nimport { evaluateSessionInvalidationFence } from '~wildo-backend-lib/security/session-invalidation-fence.backend.utils';\nimport {\n getUserAuthorizationVersion,\n type AuthorizationVersionedUser,\n} from '~wildo-backend-lib/security/authentications/models/user-authorization-version.backend.utils';\n\n/** L3: Default max concurrent refresh tokens per user. */\nconst DEFAULT_MAX_CONCURRENT_SESSIONS = 10;\n\n/**\n * Result of token issuance.\n */\nexport interface AuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresIn?: number;\n}\n\n/**\n * A-9 — the machine-readable signal a refresh-token REUSE rejection carries so a CALLER can audit the\n * theft. This issuer performs the family-wide revocation itself but deliberately does NOT audit\n * (see the class JSDoc — TRAP 13: callers decide what to audit); instead it TAGS its thrown rejection\n * with this signal, and each refresh caller reads it via {@link extractRefreshTokenReuseSignal} and\n * emits `USER_REFRESH_TOKEN_REUSE_DETECTED`. Never carries the raw token — only the reused `jti`.\n */\nexport interface RefreshTokenReuseSignal {\n /** The user whose whole token family was revoked (the victim). */\n userId: string;\n /** The reused refresh token's `jti` (the replayed credential id). */\n reusedJti: string;\n /** The session the reused token belonged to, when present on the claim. */\n sessionId?: string;\n /** Whether the `TOKEN_INVALID_BEFORE` family-revoke was written (the action the issuer took). */\n familyRevoked: boolean;\n}\n\n/**\n * Symbol-keyed so the signal rides the thrown rejection object WITHOUT ever appearing in a string-key\n * serialization or the error sanitizer (the reuse detail is internal, for the auditing caller only).\n * `Symbol.for` (the global registry, not a per-module `Symbol()`) so tag ⇄ extract still match if this\n * module is loaded under both ESM and CJS.\n */\nconst REFRESH_TOKEN_REUSE_SIGNAL_KEY = Symbol.for('wildo.auth.refreshTokenReuseSignal');\n\n/**\n * Tag a refresh-rejection error with the reuse signal (A-9). The issuer calls this after performing the\n * family revoke; the CALLER reads it back and emits the audit — preserving the issuer's no-audit boundary.\n */\nexport function tagRefreshTokenReuseSignal<E extends object>(error: E, signal: RefreshTokenReuseSignal): E {\n Object.defineProperty(error, REFRESH_TOKEN_REUSE_SIGNAL_KEY, {\n value: signal,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n return error;\n}\n\n/**\n * Read the refresh-reuse signal off a caught error, or `null` when it is not a reuse rejection (a plain\n * revoked/expired/invalid refresh). A caller uses this to emit `USER_REFRESH_TOKEN_REUSE_DETECTED` for\n * the theft case ONLY, then map the error to its own rejection.\n */\nexport function extractRefreshTokenReuseSignal(error: unknown): RefreshTokenReuseSignal | null {\n if (error !== null && typeof error === 'object' && REFRESH_TOKEN_REUSE_SIGNAL_KEY in error) {\n return (error as Record<typeof REFRESH_TOKEN_REUSE_SIGNAL_KEY, RefreshTokenReuseSignal>)[REFRESH_TOKEN_REUSE_SIGNAL_KEY];\n }\n return null;\n}\n\n/**\n * AuthTokenIssuerBackendService\n *\n * Responsibilities:\n * - Issue access tokens (delegates to JwtBackendService)\n * - Issue refresh tokens (delegates to JwtBackendService)\n *\n * This service does NOT:\n * - Manage sessions (that's AuthSessionStore)\n * - Verify credentials (that's AuthCredentialVerifier)\n * - Audit log (TRAP 13: callers decide what to audit based on full context)\n */\n@injectable()\nexport class AuthTokenIssuerBackendService {\n private readonly logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) private readonly appConfigService: AppConfigurationService,\n @inject(SAAS_SERVICE_TYPES.JwtBackendService) private readonly jwtService: JwtBackendService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.RedisService) private readonly redisService: RedisService,\n @inject(SAAS_SERVICE_TYPES.ServicesRegistryHandlerService) private readonly servicesRegistryHandler: ServicesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthExecutionContextFactory) private readonly authEcFactory: AuthExecutionContextFactoryBackendService,\n /*\n * The audit trail, `@optional()` and APPENDED LAST — the constructor-parameter discipline, and\n * optional for a second reason: this service sits on the token-issuance path, so a host that\n * binds no audit service must still be able to issue tokens. An observer may never be able to\n * fail the thing it observes.\n */\n @inject(SAAS_SERVICE_TYPES.AuditLogsService) @optional()\n private readonly auditLogsService: AuditLogsBackendService | null = null,\n ) {\n this.logDebug = this.logger.debugByType('AuthTokenIssuer');\n this.logger.setDebugTypeLogging('AuthTokenIssuer', false);\n }\n\n /**\n * Issue access + refresh tokens for a completed authentication session.\n */\n public async issueTokens(\n session: AuthenticationSession,\n options: { includeRefreshToken?: boolean; lastAuthenticatedAt?: number; activeUserType?: string } = {},\n ): Promise<AuthTokens> {\n const userId = session.userId;\n\n if (!userId) {\n throw this.errorBuilder.buildError(ErrorType.AUTHENTICATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_USER_CREDENTIALS_NOT_FOUND,\n context: { missingField: 'userId' },\n });\n }\n\n return this._issueTokensForUser(userId, session.sessionId, options.includeRefreshToken ?? true, {\n lastAuthenticatedAt: options.lastAuthenticatedAt,\n activeUserType: options.activeUserType ?? session.activeUserType ?? undefined,\n });\n }\n\n /**\n * Issue tokens for an user directly (without a full session).\n * Used by OAuth2/SAML/registration callback flows (TRAP 12) which bypass the session state machine.\n * `activeUserType` is resolved internally from the user record when not provided.\n */\n public async issueTokensForUser(\n userId: string,\n sessionId?: string,\n stepUpClaims?: {\n lastAuthenticatedAt?: number;\n activeUserType?: string;\n sessionType?: SessionType;\n },\n options?: {\n skipSessionTracking?: boolean;\n refreshTokenExpiresAt?: Date;\n },\n ): Promise<AuthTokens> {\n return this._issueTokensForUser(userId, sessionId, true, stepUpClaims, options);\n }\n\n /**\n * Internal implementation for token issuance.\n * Resolves `activeUserType` from the user DB record when not provided by the caller (N-2).\n */\n private async _issueTokensForUser(\n userId: string,\n sessionId?: string,\n includeRefreshToken: boolean = true,\n stepUpClaims?: {\n lastAuthenticatedAt?: number;\n activeUserType?: string;\n sessionType?: SessionType;\n },\n options?: {\n skipSessionTracking?: boolean;\n refreshTokenExpiresAt?: Date;\n /**\n * Refresh rotation passes the predecessor's durable authorization\n * version. Rechecking it on the issuance read closes the gap between the\n * refresh preflight read and successor signing.\n */\n expectedAuthorizationVersion?: number;\n },\n ): Promise<AuthTokens> {\n let emailVerified: boolean | undefined;\n let resolvedActiveUserType = stepUpClaims?.activeUserType;\n\n // Token issuance is a security decision, so the User read and its durable\n // version are mandatory. A best-effort read here would mint an unfenced\n // token precisely when the database boundary is unavailable.\n const tokenEc = await this.authEcFactory.createForAuthenticatedAuthOperation(\n userId,\n this.authEcFactory.getUsersReadOperationPath(),\n );\n const user =\n await this.servicesRegistryHandler.read<AuthorizationVersionedUser>(\n CoreResourceType.USERS,\n tokenEc,\n { _id: userId },\n );\n const authorizationVersion = getUserAuthorizationVersion(user);\n if (!user || authorizationVersion === null) {\n // Two very different faults, deliberately reported apart. Collapsing them into one reason\n // string sent a production investigation looking for a missing version field when the row\n // itself may simply not have been readable — the caller-facing answer is identical either way\n // (fail closed), but the operator-facing one is not: \"no such row\" points at the read's scope\n // or at write visibility, \"no version\" points at the persistence contract.\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n context: {\n reason: user\n ? 'user_authorization_version_unreadable'\n : 'user_row_not_readable_for_issuance',\n userId,\n // Only ever the SHAPE of what came back — never the value, which is an authorization\n // fence and does not belong in a log line.\n authorizationVersionType: typeof (user as AuthorizationVersionedUser | null)?._version,\n userStatus: user?.status,\n },\n },\n );\n }\n if (\n options?.expectedAuthorizationVersion !== undefined\n && authorizationVersion !== options.expectedAuthorizationVersion\n ) {\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n context: {\n reason: 'user_authorization_version_changed_during_issuance',\n userId,\n },\n },\n );\n }\n\n if (user.status === UserStatus.ACTIVE) {\n emailVerified = true;\n } else if (user.status === UserStatus.PENDING_VERIFICATION) {\n emailVerified = false;\n }\n\n // A caller-carried active type is valid only while the live User still\n // owns it. This prevents direct issuance from preserving a removed auth\n // policy even outside refresh rotation.\n if (resolvedActiveUserType && !user.userTypes?.includes(resolvedActiveUserType)\n ) {\n resolvedActiveUserType = undefined;\n }\n if (!resolvedActiveUserType && user.userTypes?.length) {\n resolvedActiveUserType = user.userTypes[0];\n }\n\n const lastAuthenticatedAt = stepUpClaims?.lastAuthenticatedAt;\n\n const accessClaim = JwtClaim_App_Auth_UserRequest_Schema.parse({\n type: ExecutionContext_ExecutionType.USER_REQUEST,\n userId,\n authorizationVersion,\n sessionType: stepUpClaims?.sessionType ?? SessionType.PERSISTENT,\n ...(emailVerified !== undefined ? { emailVerified } : {}),\n ...(lastAuthenticatedAt !== undefined ? { lastAuthenticatedAt } : {}),\n ...(resolvedActiveUserType ? { activeUserType: resolvedActiveUserType } : {}),\n // revocation-C: carry the session id onto the ACCESS claim (it was already on the refresh claim), so the\n // access-token guard can enforce a per-session revocation fence and the OIDC id_token can carry `sid`.\n ...(sessionId ? { sessionId } : {}),\n });\n\n const accessToken = await this.jwtService.createJwtForAppAuth(accessClaim);\n // Resolved AppConfiguration always has this field (required on\n // `AppConfiguration_JWT_Schema`); operator-authored value flows from\n // `wildo.infra.<env>.config.ts` `serviceAuth.accessTokenExpirationMinutes`\n // through apps-manager bootstrap material and is overlaid by\n // `app-configuration-loader.backend.service.ts`. No literal fallback\n // here on purpose — F-24 (Slice 3.3) made `serviceAuth` the single\n // source of truth, so a missing value should fail the boot rather than\n // silently downgrade to a 15-minute floor.\n const accessTokenMinutes = this.appConfigService.config.jwt.accessTokenExpirationMinutes;\n const accessTokenExpiresInSeconds = accessTokenMinutes * 60;\n this.logDebug('Access token issued', { userId, expiresIn: accessTokenExpiresInSeconds });\n\n let refreshToken: string | undefined;\n if (includeRefreshToken) {\n const jti = crypto.randomUUID();\n const refreshClaim: Jwt_RefreshToken_CreationParameter = {\n type: ExecutionContext_ExecutionType.USER_REQUEST,\n userId,\n authorizationVersion,\n jti,\n sessionId: sessionId,\n sessionType: stepUpClaims?.sessionType ?? SessionType.PERSISTENT,\n ...(lastAuthenticatedAt !== undefined ? { lastAuthenticatedAt } : {}),\n ...(resolvedActiveUserType ? { activeUserType: resolvedActiveUserType } : {}),\n ...(options?.refreshTokenExpiresAt ? { refreshTokenExpiresAt: Math.floor(options.refreshTokenExpiresAt.getTime() / 1000) } : {}),\n };\n\n refreshToken = await this.jwtService.createRefreshToken(\n refreshClaim,\n options?.refreshTokenExpiresAt\n ? { expiresInSeconds: Math.max(1, Math.floor((options.refreshTokenExpiresAt.getTime() - Date.now()) / 1000)) }\n : undefined,\n );\n\n if (!options?.skipSessionTracking) {\n await this._trackAndEnforceSessionLimit(userId, jti, resolvedActiveUserType);\n }\n\n this.logDebug('Refresh token issued', { userId, sessionType: stepUpClaims?.sessionType });\n }\n\n return {\n accessToken,\n refreshToken,\n expiresIn: accessTokenExpiresInSeconds,\n };\n }\n\n /**\n * Refresh an access token using a valid refresh token.\n *\n * Flow:\n * 1. Verify the refresh token JWT signature + expiration\n * 2. Parse claims using JwtClaim_App_RefreshToken_Schema (discriminated union)\n * 3. Issue a fresh access token for the same user\n * 4. Optionally rotate the refresh token (always rotate for security)\n *\n * @param refreshToken - The JWT refresh token string\n * @returns Fresh access + refresh tokens\n * @throws AUTHENTICATION error if the refresh token is invalid/expired\n */\n public async refreshAccessToken(refreshToken: string): Promise<AuthTokens> {\n // Step 1: Verify the refresh token JWT (signature + expiry + audience=REFRESH_TOKEN)\n let decoded: unknown;\n try {\n decoded = await this.jwtService.verifyRefreshToken(refreshToken);\n } catch (error) {\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // Step 2: Parse claims using the discriminated union schema\n const parseResult = JwtClaim_App_RefreshToken_Schema.safeParse(decoded);\n if (!parseResult.success) {\n this.logger.warn('Refresh token claims failed validation', { issues: parseResult.error.issues });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n const claims = parseResult.data;\n\n // Step 2b: Read the live User and enforce the durable authorization fence\n // BEFORE the rotation-grace early return. A cached successor must not be\n // returned after userTypes/appRoles/status changed.\n const ec = await this.authEcFactory.createForAuthenticatedAuthOperation(\n claims.userId,\n this.authEcFactory.getUsersReadOperationPath(),\n );\n const currentUser =\n await this.servicesRegistryHandler.read<AuthorizationVersionedUser>(\n CoreResourceType.USERS,\n ec,\n { _id: claims.userId },\n );\n const currentAuthorizationVersion = getUserAuthorizationVersion(currentUser);\n if (!currentUser || currentUser.status !== UserStatus.ACTIVE) {\n this.logger.warn('Refresh token for non-active user', {\n jti: claims.jti,\n userId: claims.userId,\n userStatus: currentUser?.status ?? 'NOT_FOUND',\n });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_ACCOUNT_LOCKED,\n context: {\n userStatus: currentUser?.status ?? 'NOT_FOUND',\n source: 'token_refresh',\n },\n },\n );\n }\n if (\n currentAuthorizationVersion === null\n || currentAuthorizationVersion !== claims.authorizationVersion\n ) {\n this.logger.warn(\n 'Refresh token authorization version no longer matches User',\n {\n jti: claims.jti,\n userId: claims.userId,\n tokenAuthorizationVersion: claims.authorizationVersion,\n currentAuthorizationVersion,\n },\n );\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n context: { reason: 'user_authorization_version_changed' },\n },\n );\n }\n\n // Keep explicit all-session invalidation (password reset / theft\n // detection) ahead of rotation grace as well. AuthorizationVersion closes\n // database authorization races; this timestamp remains the independent\n // credential/session revocation mechanism.\n // GRACE-BOUNDARY (fixed here 2026-07-29, moved to the shared owner 2026-08-01). The fail-closed\n // `<=` — and the full rationale for why the same second must be rejected, and why raising timestamp\n // precision alone would NOT fix it — now lives in {@link evaluateUserTokenInvalidationFence}, which\n // every auth front shares. It was six hand-written copies of this comparison that let the fix land on\n // two of them while four kept the bypassing `<`. Refresh-specific note: this fence is checked BEFORE\n // rotation grace, so a stolen chain cannot ride the grace window past an invalidation.\n const { isRevoked, invalidBefore } = await evaluateUserTokenInvalidationFence(\n this.redisService, claims.userId, claims.iat,\n );\n if (isRevoked) {\n this.logger.warn('Refresh token issued before user-level invalidation', {\n jti: claims.jti,\n userId: claims.userId,\n tokenIat: claims.iat,\n invalidBefore,\n });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // revocation-C: PER-SESSION fence. Distinct from the user-wide fence above — a single-session\n // terminate (OIDC end_session / per-device logout) evicts THIS session's refresh chain while the\n // user's other sessions stay live. Checked here so the terminated session cannot rotate a new\n // access token; the access guard checks the same fence so the current access token also dies.\n const sessionFence = await evaluateSessionInvalidationFence(\n this.redisService, claims.sessionId, claims.iat,\n );\n if (sessionFence.isRevoked) {\n this.logger.warn('Refresh token issued before per-session invalidation', {\n jti: claims.jti,\n userId: claims.userId,\n sessionId: claims.sessionId,\n tokenIat: claims.iat,\n invalidBefore: sessionFence.invalidBefore,\n });\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference:\n ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // Step 2c: Check JTI blacklist (revoked tokens) with reuse detection.\n const blacklistValue = await this.redisService.get<string>(`${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${claims.jti}`);\n if (blacklistValue) {\n if (blacklistValue === RefreshTokenBlacklistReason.ROTATED) {\n // IDEMPOTENT-ROTATION GRACE (checked BEFORE theft revocation): a token\n // re-presented within {@link REFRESH_TOKEN_ROTATION_GRACE_SECONDS} of\n // its rotation is almost always a benign race — a lost/retried refresh\n // response, or a second browsing context that read the shared\n // `__wildo_refresh` cookie a beat before the rotation replaced it. In\n // that case the original rotation cached its successor tokens; return\n // that SAME successor so the racing client converges on one token\n // family instead of being force-logged-out. Genuine theft re-presents\n // the token long after this window, by which time the grace key has\n // expired and control falls through to full revocation below.\n const graceSuccessor = await this.redisService.get<AuthTokens>(\n `${AUTH_REDIS_KEYS.REFRESH_GRACE_SUCCESSOR}${claims.jti}`,\n );\n if (graceSuccessor) {\n // Both durable authorizationVersion and explicit Redis invalidation\n // were checked above. Returning the exact cached successor therefore\n // grants nothing beyond the legitimate rotation winner and cannot\n // bypass an intervening User authorization mutation.\n this.logger.info('Refresh-token reuse within rotation grace window — returning cached successor (benign race, NOT revoking)', {\n jti: claims.jti,\n userId: claims.userId,\n type: claims.type,\n });\n return graceSuccessor;\n }\n\n // REUSE DETECTED outside the grace window: the old refresh token may\n // have been stolen — revoke ALL tokens for this user.\n this.logger.warn('REFRESH TOKEN REUSE DETECTED — possible token theft, revoking all tokens for user', {\n jti: claims.jti,\n userId : claims.userId,\n type: claims.type,\n });\n // F-24 (Slice 3.3): refresh-token TTL is required on the resolved\n // AppConfiguration and authoritatively sourced from operator-authored\n // `serviceAuth.refreshTokenExpirationDays`; no literal fallback.\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.TOKEN_INVALID_BEFORE}${claims.userId}`,\n Math.floor(Date.now() / 1000),\n { ttl: refreshTokenDays * 86400 },\n );\n // A-9: this issuer performed the family revoke but does NOT audit (TRAP 13 — see class JSDoc).\n // TAG the rejection with the reuse signal so the CALLER emits `USER_REFRESH_TOKEN_REUSE_DETECTED`\n // for this theft (the plain-revoked `else` below throws the UNtagged twin, so a routine revoked\n // token is not mis-audited as theft). Distinct throw here — never falls through to the shared one.\n // assurance-control: WILDO.IDENTITY.CREDENTIAL_LIFECYCLE — credential-theft detection + response.\n throw tagRefreshTokenReuseSignal(\n this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN },\n ) as object,\n {\n userId: claims.userId,\n reusedJti: claims.jti,\n sessionId: claims.sessionId,\n familyRevoked: true,\n },\n );\n } else {\n this.logger.warn('Refresh token has been revoked', { jti: claims.jti });\n }\n throw this.errorBuilder.buildError(\n ErrorType.AUTHENTICATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.AUTHENTICATION_INVALID_TOKEN,\n },\n );\n }\n\n // Step 3: Issue new tokens — carry forward session-scoped claims, but\n // require the issuance read to observe this same authorization version.\n this.logDebug('Refresh token validated, issuing new tokens', { userId : claims.userId, jti: claims.jti });\n const refreshLastAuthAt = claims.lastAuthenticatedAt;\n const refreshActiveUserType = claims.activeUserType;\n const refreshSessionType = claims.sessionType;\n const refreshTokenExpiresAtClaim = claims.refreshTokenExpiresAt;\n\n const newTokens = await this._issueTokensForUser(\n claims.userId,\n claims.sessionId,\n true,\n {\n lastAuthenticatedAt: refreshLastAuthAt,\n activeUserType: refreshActiveUserType,\n sessionType: refreshSessionType ?? SessionType.PERSISTENT,\n },\n {\n ...(refreshSessionType === SessionType.ONE_OFF ? { skipSessionTracking: true } : {}),\n ...(refreshTokenExpiresAtClaim ? { refreshTokenExpiresAt: new Date(refreshTokenExpiresAtClaim * 1000) } : {}),\n expectedAuthorizationVersion: claims.authorizationVersion,\n },\n );\n\n // Step 4: Blacklist the consumed refresh token JTI as 'rotated'\n const nowSeconds = Math.floor(Date.now() / 1000);\n const ttl = claims.exp ? claims.exp - nowSeconds : 86400;\n if (ttl > 0 && claims.jti) {\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${claims.jti}`,\n RefreshTokenBlacklistReason.ROTATED,\n { ttl },\n );\n this.logDebug('Old refresh token JTI blacklisted after rotation', { jti: claims.jti, ttl });\n\n // Cache the successor under the consumed JTI for the idempotent-rotation\n // grace window so a benign reuse race returns THIS exact successor (see\n // Step 2b) instead of being revoked. The TTL is the grace window clamped\n // to the consumed token's own remaining lifetime — the grace entry is\n // ALWAYS shorter-lived than the blacklist entry above, so once it expires\n // a re-presented token still trips full revocation (theft is preserved).\n //\n // AT-REST EXPOSURE (accepted, bounded): this is the ONLY place the auth\n // subsystem writes live token material to Redis — `REFRESH_BLACKLIST`\n // holds only reason strings, `TOKEN_INVALID_BEFORE` timestamps,\n // `ACTIVE_REFRESH_JTIS` only JTIs, and the session store holds session\n // state, never tokens. We store the FULL successor (incl. the refresh\n // token) because the idempotent guarantee requires returning the exact\n // same tokens — re-issuing would mint a divergent family, and an\n // access-token-only cache would break the lost-rotation-response client\n // (its cookie is still the now-blacklisted predecessor, so it needs the\n // successor refresh token re-sent via `Set-Cookie` to advance). An\n // attacker with Redis read access (snapshot/AOF dump, MONITOR, leaked\n // backup) could therefore harvest refresh tokens minted in the last\n // `graceTtl` seconds — but such an attacker already reads the session\n // store, so the marginal blast radius is one short window. If that window\n // ever needs hardening, encrypt this blob at rest (the read site already\n // fails closed to revocation on a parse miss). It is NOT logged.\n const graceTtl = Math.min(REFRESH_TOKEN_ROTATION_GRACE_SECONDS, ttl);\n if (graceTtl > 0) {\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.REFRESH_GRACE_SUCCESSOR}${claims.jti}`,\n newTokens,\n { ttl: graceTtl },\n );\n this.logDebug('Cached rotation successor for idempotent-grace window', { jti: claims.jti, graceTtl });\n }\n }\n\n return newTokens;\n }\n\n /**\n * Revoke a refresh token by adding its JTI to the Redis blacklist.\n * TTL matches the token's remaining lifetime so the entry auto-expires.\n *\n * Called from the logout route handler.\n */\n public async revokeRefreshToken(refreshToken: string): Promise<void> {\n // Decode the token to extract JTI and exp. We verify the signature\n // so that arbitrary strings can't pollute the blacklist.\n let decoded: unknown;\n try {\n decoded = await this.jwtService.verifyRefreshToken(refreshToken);\n } catch {\n // Token is already invalid/expired — nothing to revoke\n this.logDebug('Refresh token revocation skipped — token is invalid or expired');\n return;\n }\n\n const parseResult = JwtClaim_App_RefreshToken_Schema.safeParse(decoded);\n if (!parseResult.success) {\n this.logDebug('Refresh token revocation skipped — claims are malformed');\n return;\n }\n\n const { jti, exp } = parseResult.data;\n if (!jti) {\n this.logDebug('Refresh token revocation skipped — no JTI');\n return;\n }\n\n // TTL = remaining seconds until token naturally expires\n const nowSeconds = Math.floor(Date.now() / 1000);\n const ttl = exp ? (exp as number) - nowSeconds : 86400; // fallback 24h\n if (ttl <= 0) {\n this.logDebug('Refresh token already expired — no blacklist needed', { jti });\n return;\n }\n\n await this.redisService.set(`${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${jti}`, RefreshTokenBlacklistReason.REVOKED, { ttl });\n\n // L3: Remove from active refresh tokens set\n const claims = parseResult.data;\n const userId = claims.userId;\n try {\n const setKey = this.redisService.buildKey(`${AUTH_REDIS_KEYS.ACTIVE_REFRESH_JTIS}${userId}`);\n await this.redisService.eval('redis.call(\"ZREM\", KEYS[1], ARGV[1]); return 1', [setKey], [jti]);\n } catch {\n // Non-fatal\n }\n\n this.logDebug('Refresh token revoked', { jti, ttlSeconds: ttl });\n }\n\n /**\n * Invalidate all refresh tokens for an user by setting a per-user\n * \"invalid before\" timestamp in Redis. All refresh tokens with `iat` before\n * this timestamp will be rejected on next use.\n *\n * Used on password change/reset (C4) and token reuse detection (H3).\n * TTL matches operator-authored `serviceAuth.refreshTokenExpirationDays`\n * (F-24, Slice 3.3); no literal fallback.\n */\n public async invalidateAllTokensForUser(userId: string): Promise<void> {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.TOKEN_INVALID_BEFORE}${userId}`,\n nowSeconds,\n { ttl: refreshTokenDays * 86400 },\n );\n this.logDebug('All tokens invalidated for user', { userId, invalidBefore: nowSeconds });\n }\n\n /**\n * revocation-C: invalidate a SINGLE login session by setting a per-session \"invalid before\"\n * timestamp in Redis. Every access/refresh token whose `sessionId` matches AND whose `iat` is at or\n * before this timestamp is rejected on next use — at every first-party auth front: the refresh exchange\n * ({@link refreshAccessToken}), the first-party access guard (`auth-jwt-guard.extractAuthenticatedUser`),\n * and the manual-controller / resource-API / socket-handshake fronts (`execution-context-creator`), all\n * via {@link evaluateSessionInvalidationFence}.\n *\n * This is the least-privilege sibling of {@link invalidateAllTokensForUser}: it terminates ONE\n * session (the OIDC `end_session_endpoint` / RP-Initiated Logout, and per-device logout), leaving the\n * user's other sessions live. TTL matches operator-authored `jwt.refreshTokenExpirationDays` — the\n * fence only needs to outlive the longest-lived token that could still carry this `sessionId`, after\n * which no such token can exist and the key is dead weight.\n */\n public async invalidateSession(sessionId: string): Promise<void> {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.SESSION_INVALID_BEFORE}${sessionId}`,\n nowSeconds,\n { ttl: refreshTokenDays * 86400 },\n );\n this.logDebug('Session invalidated (per-session fence set)', { sessionId, invalidBefore: nowSeconds });\n }\n\n /**\n * L3: Track an active refresh token JTI for an user and enforce the concurrent session limit.\n *\n * Uses a Redis sorted set per user where:\n * - Members = JTI strings\n * - Scores = issuedAt in MILLISECONDS (for FIFO eviction)\n *\n * When the set exceeds the max size (default 10), the oldest JTI(s) are evicted\n * and blacklisted so they can't be used anymore.\n *\n * The ordering, the score's unit, and the blacklist TTL all live in\n * `concurrent-session-eviction.backend.utils` — retiring the OLDEST chain is the security property\n * the cap exists to provide, so it is a named, separately-tested policy rather than a detail of\n * this method. See {@link buildSessionOrderingScore} for why whole-second scores silently made the\n * eviction arbitrary.\n */\n private async _trackAndEnforceSessionLimit(userId: string, newJti: string, activeUserType?: string): Promise<void> {\n const userTypeConfig = activeUserType\n ? this.appConfigService.config.userTypes?.[activeUserType]\n : undefined;\n const configuredMax = userTypeConfig?.auth?.maxConcurrentSessions;\n const maxSessions = (configuredMax && configuredMax > 0) ? configuredMax : DEFAULT_MAX_CONCURRENT_SESSIONS;\n // F-24 (Slice 3.3): operator-authored TTL; no literal fallback.\n const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;\n const setTtl = refreshTokenDays * 86400;\n const nowMilliseconds = buildSessionOrderingScore();\n const setKey = this.redisService.buildKey(`${AUTH_REDIS_KEYS.ACTIVE_REFRESH_JTIS}${userId}`);\n\n try {\n const evictedPairs = await this.redisService.eval(\n CONCURRENT_SESSION_EVICTION_LUA,\n [setKey],\n [newJti, String(nowMilliseconds), String(maxSessions), String(setTtl)],\n ) as string[] | null;\n\n if (evictedPairs && evictedPairs.length > 0) {\n // evictedPairs is [jti, score, jti, score, ...] — blacklist each with its actual remaining TTL\n for (let i = 0; i < evictedPairs.length; i += 2) {\n const jti = evictedPairs[i];\n const remainingTtl = resolveEvictedJtiBlacklistSeconds({\n rawScore: evictedPairs[i + 1],\n nowMilliseconds,\n refreshTokenTtlSeconds: setTtl,\n });\n await this.redisService.set(\n `${AUTH_REDIS_KEYS.REFRESH_BLACKLIST}${jti}`,\n RefreshTokenBlacklistReason.EVICTED,\n { ttl: remainingTtl },\n );\n }\n this.logDebug('Concurrent session limit enforced — evicted oldest tokens', {\n userId,\n evictedCount: evictedPairs.length / 2,\n maxSessions,\n });\n this.recordEvictions({ userId, evictedPairs, maxSessions, nowMilliseconds });\n }\n } catch (error) {\n // Non-fatal: don't block token issuance if tracking fails\n this.logger.warn('Failed to track/enforce concurrent session limit', {\n userId,\n ...projectCaughtErrorLogFields(error),\n });\n }\n }\n\n /**\n * Record each retired refresh chain in the audit trail.\n *\n * Until 2026-09-02 the only trace of an eviction was the debug line above — a security-relevant\n * token-lifecycle act with no durable record, which is exactly what `CLAUDE.md`'s\n * assurance-by-construction rule asks for by name. The cap exists to bound the blast radius of\n * undetected token theft, so a burst of evictions for one principal is among the more interesting\n * things the trail can carry, and it was not carrying it.\n *\n * `evictedIssuedAt` is what makes the record worth having: the cap's security property is that the\n * OLDEST chain goes, and a record naming only the victim would prove a session ended without\n * proving the right one did. An unreadable score yields ABSENCE rather than a synthesised instant —\n * a fabricated timestamp would make the first-in-first-out claim unfalsifiable.\n *\n * Feature-DETECTED rather than null-checked, for the reason recorded at the cross-tenant emitter:\n * adding a `log*` method breaks partially-mocked doubles, the property is absent, and the CALL\n * throws synchronously before any `.catch()` can make it fire-and-forget.\n *\n * assurance-control: WILDO.IDENTITY.SESSION_CONCURRENCY_LIMIT — the bound's enforcement is\n * evidenced, naming WHICH chain was retired and when it was issued.\n */\n private recordEvictions(input: {\n userId: string;\n evictedPairs: string[];\n maxSessions: number;\n nowMilliseconds: number;\n }): void {\n const auditLogsService = this.auditLogsService;\n if (typeof auditLogsService?.logUserSessionEvicted !== 'function') return;\n\n for (let index = 0; index < input.evictedPairs.length; index += 2) {\n const evictedJti = input.evictedPairs[index];\n if (!evictedJti) continue;\n const issuedAtMilliseconds = readIssuedAtMilliseconds(input.evictedPairs[index + 1]);\n try {\n void auditLogsService.logUserSessionEvicted({\n userId: input.userId,\n evictedJti,\n ...(issuedAtMilliseconds === undefined ? {} : { evictedIssuedAt: new Date(issuedAtMilliseconds) }),\n configuredMaximum: input.maxSessions,\n retainedCount: input.maxSessions,\n }).catch(() => undefined);\n } catch {\n /* An observer must never fail the issuance it observes. */\n }\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"initiator-organization-confinement.backend.d.ts","sourceRoot":"","sources":["../../../../../src/security/authorizations/initiator-organization-confinement.backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,oGAAoG;AACpG,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,uCAAuC,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAC5F;AAED,qFAAqF;AACrF,MAAM,WAAW,4CAA4C;IAC3D,iCAAiC,CAAC,UAAU,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACtF,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACrD,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACpE;AAED,sFAAsF;AACtF,eAAO,MAAM,wCAAwC,mBAAmB,CAAC;AAEzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,8CAA8C,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,EAAE;IAC7F,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,sCAAsC,CAAC;IAClE,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,4CAA4C,CAAC;IACnE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3D,GAAG,OAAO,CAkCV"}
1
+ {"version":3,"file":"initiator-organization-confinement.backend.d.ts","sourceRoot":"","sources":["../../../../../src/security/authorizations/initiator-organization-confinement.backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,oGAAoG;AACpG,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,uCAAuC,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAC5F;AAED,qFAAqF;AACrF,MAAM,WAAW,4CAA4C;IAC3D,iCAAiC,CAAC,UAAU,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACtF,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACrD,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACpE;AAED,sFAAsF;AACtF,eAAO,MAAM,wCAAwC,mBAAmB,CAAC;AAEzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,8CAA8C,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,EAAE;IAC7F,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,sCAAsC,CAAC;IAClE,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,4CAA4C,CAAC;IACnE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3D,GAAG,OAAO,CAyDV"}
@@ -59,7 +59,30 @@ export function assignInitiatorOrganizationConfinementToFilter(params) {
59
59
  || !params.isFieldPersisted(INITIATOR_ORGANIZATION_CONFINEMENT_FIELD)) {
60
60
  return false;
61
61
  }
62
- const organizationPaths = params.schemaPaths.findNestedFieldPathsFromZodSchema(params.mainSchema, INITIATOR_ORGANIZATION_CONFINEMENT_FIELD);
62
+ /*
63
+ * TOP-LEVEL PATHS ONLY — a correction measured on 2026-09-01, in both directions at once.
64
+ *
65
+ * `findNestedFieldPathsFromZodSchema` returns EVERY schema path ending in `organizationId`,
66
+ * and the first control-plane resource to reach this rule over HTTP
67
+ * (`wildoApplicationActorTasks`) models one NESTED occurrence:
68
+ * `errorStates.errorItems.initiatorIds.organizationId` — a RECORDED FACT about a past error's
69
+ * initiator, not a tenancy column. Confining on it failed differently per adapter, and the loud
70
+ * failure was the harmless one:
71
+ *
72
+ * - PostgreSQL: `invalid column reference errorStates.errorItems.initiatorIds.organizationId`
73
+ * — a 400 on every LIST. Visible, kind.
74
+ * - MongoDB: `{ "errorStates…organizationId": org }` matches only rows where a nested error
75
+ * item CARRIES that value — so every row with no recorded errors would silently vanish from
76
+ * its OWN tenant's listings. Invisible, and the dangerous direction
77
+ * (`negative-results-require-a-positive-probe.md`: under-reporting reads as truth).
78
+ *
79
+ * Top-level is not an approximation of the rule — it IS the rule. The write path's own
80
+ * invariant (documented at the contextual-filter builder beside
81
+ * `enrichDataWithContextFields`) is that a scoped resource always carries its scope FK as a
82
+ * top-level column; tenancy never lives only inside a nested value. A deeper occurrence of the
83
+ * field NAME is data about something else and was never this confinement's subject.
84
+ */
85
+ const organizationPaths = params.schemaPaths.findNestedFieldPathsFromZodSchema(params.mainSchema, INITIATOR_ORGANIZATION_CONFINEMENT_FIELD).filter((fullPath) => !fullPath.includes('.'));
63
86
  let applied = false;
64
87
  organizationPaths.forEach((fullPath) => {
65
88
  if (!params.schemaPaths.hasNestedPath(params.filter, fullPath)) {