hexcore 11.2.0__tar.gz → 11.3.0__tar.gz

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 (397) hide show
  1. {hexcore-11.2.0/hexcore.egg-info → hexcore-11.3.0}/PKG-INFO +1 -1
  2. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/config.py +29 -3
  3. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/api/routers.py +62 -1
  4. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/fastapi.py +4 -0
  5. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/fastapi.pyi +3 -1
  6. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/app.py +34 -9
  7. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/middlewares.py +64 -0
  8. {hexcore-11.2.0 → hexcore-11.3.0/hexcore.egg-info}/PKG-INFO +1 -1
  9. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore.egg-info/SOURCES.txt +1 -0
  10. {hexcore-11.2.0 → hexcore-11.3.0}/pyproject.toml +1 -1
  11. hexcore-11.3.0/tests/test_api_cors_dinamico.py +250 -0
  12. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_http.py +164 -0
  13. {hexcore-11.2.0 → hexcore-11.3.0}/LICENSE +0 -0
  14. {hexcore-11.2.0 → hexcore-11.3.0}/MANIFEST.in +0 -0
  15. {hexcore-11.2.0 → hexcore-11.3.0}/README.md +0 -0
  16. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/__init__.py +0 -0
  17. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/__main__.py +0 -0
  18. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/_deprecation.py +0 -0
  19. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/__init__.py +0 -0
  20. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/__init__.py +0 -0
  21. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/adapters.py +0 -0
  22. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/config.py +0 -0
  23. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/factory.py +0 -0
  24. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/in_memory_buses.py +0 -0
  25. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/pipeline.py +0 -0
  26. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/registry.py +0 -0
  27. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/cqrs/scheduler.py +0 -0
  28. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/dtos/__init__.py +0 -0
  29. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/dtos/base.py +0 -0
  30. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/dtos/cursor.py +0 -0
  31. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/dtos/errors.py +0 -0
  32. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/dtos/query.py +0 -0
  33. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/eventsourcing/__init__.py +0 -0
  34. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/eventsourcing/config.py +0 -0
  35. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/eventsourcing/factory.py +0 -0
  36. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/eventsourcing/projector.py +0 -0
  37. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/eventsourcing/relay.py +0 -0
  38. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/eventsourcing/repository.py +0 -0
  39. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/use_cases/__init__.py +0 -0
  40. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/use_cases/base.py +0 -0
  41. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/application/use_cases/query.py +0 -0
  42. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/capabilities.py +0 -0
  43. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/cqrs.py +0 -0
  44. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/cqrs.pyi +0 -0
  45. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/__init__.py +0 -0
  46. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/__init__.pyi +0 -0
  47. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/__init__.py +0 -0
  48. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/authorization.py +0 -0
  49. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/authz_middleware.py +0 -0
  50. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/commands.py +0 -0
  51. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/config.py +0 -0
  52. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/container.py +0 -0
  53. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/hooks.py +0 -0
  54. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/plugins.py +0 -0
  55. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/application/services.py +0 -0
  56. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/__init__.py +0 -0
  57. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/authorization.py +0 -0
  58. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/context.py +0 -0
  59. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/entities.py +0 -0
  60. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/events.py +0 -0
  61. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/exceptions.py +0 -0
  62. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/permissions.py +0 -0
  63. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/plugins.py +0 -0
  64. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/ports.py +0 -0
  65. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/domain/value_objects.py +0 -0
  66. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/__init__.py +0 -0
  67. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/api/__init__.py +0 -0
  68. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/api/authorization.py +0 -0
  69. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/api/dependencies.py +0 -0
  70. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/api/middlewares.py +0 -0
  71. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/cli.py +0 -0
  72. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/clock.py +0 -0
  73. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/envelope.py +0 -0
  74. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/hashing.py +0 -0
  75. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/keys.py +0 -0
  76. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/lifespan.py +0 -0
  77. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/__init__.py +0 -0
  78. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/beanie/__init__.py +0 -0
  79. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/beanie/documents.py +0 -0
  80. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/beanie/repositories.py +0 -0
  81. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/beanie/schema.py +0 -0
  82. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/selection.py +0 -0
  83. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/sqlalchemy/__init__.py +0 -0
  84. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/sqlalchemy/models.py +0 -0
  85. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/sqlalchemy/models_mixins.py +0 -0
  86. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/sqlalchemy/registry.py +0 -0
  87. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/sqlalchemy/repositories.py +0 -0
  88. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/orms/sqlalchemy/schema.py +0 -0
  89. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/revocation.py +0 -0
  90. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/secretbox.py +0 -0
  91. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/tokens.py +0 -0
  92. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/infrastructure/transports.py +0 -0
  93. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/__init__.py +0 -0
  94. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/__init__.py +0 -0
  95. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/cache.py +0 -0
  96. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/conditions.py +0 -0
  97. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/domain.py +0 -0
  98. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/__init__.py +0 -0
  99. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/beanie/__init__.py +0 -0
  100. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/beanie/repository.py +0 -0
  101. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/sqlalchemy/__init__.py +0 -0
  102. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/sqlalchemy/models.py +0 -0
  103. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/sqlalchemy/models_mixins.py +0 -0
  104. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/orms/sqlalchemy/repository.py +0 -0
  105. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/pdp.py +0 -0
  106. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/pip.py +0 -0
  107. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/predicates.py +0 -0
  108. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/provider.py +0 -0
  109. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/router.py +0 -0
  110. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/drbac/service.py +0 -0
  111. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/impersonate/__init__.py +0 -0
  112. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/impersonate/commands.py +0 -0
  113. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/impersonate/domain.py +0 -0
  114. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/impersonate/router.py +0 -0
  115. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/impersonate/service.py +0 -0
  116. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/magic_link/__init__.py +0 -0
  117. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/magic_link/commands.py +0 -0
  118. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/magic_link/router.py +0 -0
  119. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/__init__.py +0 -0
  120. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/domain.py +0 -0
  121. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/http.py +0 -0
  122. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/__init__.py +0 -0
  123. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/beanie/__init__.py +0 -0
  124. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/beanie/repository.py +0 -0
  125. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/__init__.py +0 -0
  126. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/models.py +0 -0
  127. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/models_mixins.py +0 -0
  128. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/repository.py +0 -0
  129. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/providers.py +0 -0
  130. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/router.py +0 -0
  131. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/oauth/service.py +0 -0
  132. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/__init__.py +0 -0
  133. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/domain.py +0 -0
  134. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/__init__.py +0 -0
  135. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/beanie/__init__.py +0 -0
  136. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/beanie/repository.py +0 -0
  137. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/sqlalchemy/__init__.py +0 -0
  138. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/sqlalchemy/models.py +0 -0
  139. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/sqlalchemy/models_mixins.py +0 -0
  140. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/orms/sqlalchemy/repository.py +0 -0
  141. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/router.py +0 -0
  142. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/organization/service.py +0 -0
  143. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/__init__.py +0 -0
  144. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/domain.py +0 -0
  145. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/__init__.py +0 -0
  146. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/beanie/__init__.py +0 -0
  147. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/beanie/repository.py +0 -0
  148. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/__init__.py +0 -0
  149. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/models.py +0 -0
  150. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/models_mixins.py +0 -0
  151. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/repository.py +0 -0
  152. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/router.py +0 -0
  153. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/service.py +0 -0
  154. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/passkey/webauthn_adapter.py +0 -0
  155. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/__init__.py +0 -0
  156. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/cache.py +0 -0
  157. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/cli.py +0 -0
  158. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/domain.py +0 -0
  159. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/matcher.py +0 -0
  160. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/__init__.py +0 -0
  161. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/beanie/__init__.py +0 -0
  162. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/beanie/repository.py +0 -0
  163. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/sqlalchemy/__init__.py +0 -0
  164. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/sqlalchemy/models.py +0 -0
  165. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/sqlalchemy/models_mixins.py +0 -0
  166. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/orms/sqlalchemy/repository.py +0 -0
  167. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/provider.py +0 -0
  168. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/resolver.py +0 -0
  169. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/router.py +0 -0
  170. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/rbac/service.py +0 -0
  171. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/storage.py +0 -0
  172. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/__init__.py +0 -0
  173. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/commands.py +0 -0
  174. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/crypto.py +0 -0
  175. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/domain.py +0 -0
  176. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/__init__.py +0 -0
  177. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/beanie/__init__.py +0 -0
  178. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/beanie/repository.py +0 -0
  179. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/__init__.py +0 -0
  180. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/models.py +0 -0
  181. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/models_mixins.py +0 -0
  182. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/repository.py +0 -0
  183. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/router.py +0 -0
  184. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/service.py +0 -0
  185. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/plugins/two_factor/totp.py +0 -0
  186. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/testing/__init__.py +0 -0
  187. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/testing/fakes.py +0 -0
  188. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/testing/fixtures.py +0 -0
  189. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/darwin/testing/helpers.py +0 -0
  190. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/__init__.py +0 -0
  191. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/base.py +0 -0
  192. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/__init__.py +0 -0
  193. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/buses.py +0 -0
  194. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/commands.py +0 -0
  195. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/context.py +0 -0
  196. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/cron.py +0 -0
  197. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/decorators.py +0 -0
  198. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/dispatch.py +0 -0
  199. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/envelope.py +0 -0
  200. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/exceptions.py +0 -0
  201. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/handlers.py +0 -0
  202. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/middleware.py +0 -0
  203. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/queries.py +0 -0
  204. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/resolution.py +0 -0
  205. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/serializer.py +0 -0
  206. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/cqrs/task_queues.py +0 -0
  207. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/events.py +0 -0
  208. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/__init__.py +0 -0
  209. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/aggregate.py +0 -0
  210. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/exceptions.py +0 -0
  211. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/projections.py +0 -0
  212. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/snapshots.py +0 -0
  213. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/store.py +0 -0
  214. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/eventsourcing/stored.py +0 -0
  215. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/exceptions.py +0 -0
  216. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/repositories.py +0 -0
  217. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/services.py +0 -0
  218. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/domain/uow.py +0 -0
  219. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/eventsourcing.py +0 -0
  220. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/eventsourcing.pyi +0 -0
  221. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/__init__.py +0 -0
  222. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/__init__.py +0 -0
  223. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/cqrs.py +0 -0
  224. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/eventsourcing.py +0 -0
  225. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/exception_handlers.py +0 -0
  226. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/health.py +0 -0
  227. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/lifespan.py +0 -0
  228. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/rate_limit.py +0 -0
  229. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/routing.py +0 -0
  230. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/streaming.py +0 -0
  231. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/api/utils.py +0 -0
  232. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cache/__init__.py +0 -0
  233. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cache/cache_backends/__init__.py +0 -0
  234. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cache/cache_backends/memory.py +0 -0
  235. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cache/cache_backends/redis.py +0 -0
  236. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cli.py +0 -0
  237. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/__init__.py +0 -0
  238. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/cron_sql.py +0 -0
  239. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/middlewares.py +0 -0
  240. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/postgres_bus.py +0 -0
  241. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/postgres_lock.py +0 -0
  242. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/procrastinate.py +0 -0
  243. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/pydantic_serializer.py +0 -0
  244. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/rabbitmq.py +0 -0
  245. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/redis_bus.py +0 -0
  246. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/cqrs/redis_lock.py +0 -0
  247. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/__init__.py +0 -0
  248. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/beanie_documents.py +0 -0
  249. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/beanie_store.py +0 -0
  250. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/memory.py +0 -0
  251. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/redis_store.py +0 -0
  252. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/sqlalchemy_models.py +0 -0
  253. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/sqlalchemy_models_mixins.py +0 -0
  254. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/eventsourcing/sqlalchemy_store.py +0 -0
  255. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/__init__.py +0 -0
  256. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/_beanie_impl.py +0 -0
  257. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/_sqlalchemy_impl.py +0 -0
  258. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/base.py +0 -0
  259. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/decorators.py +0 -0
  260. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/implementations.py +0 -0
  261. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/__init__.py +0 -0
  262. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
  263. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/beanie/utils.py +0 -0
  264. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +0 -0
  265. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/id_types.py +0 -0
  266. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +0 -0
  267. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.py +0 -0
  268. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/repositories/utils.py +0 -0
  269. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/task_queues/__init__.py +0 -0
  270. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/task_queues/celery_adapter.py +0 -0
  271. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/task_queues/procrastinate_adapter.py +0 -0
  272. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/uow/__init__.py +0 -0
  273. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/uow/decorators.py +0 -0
  274. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/uow/helpers.py +0 -0
  275. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/uow/scopes.py +0 -0
  276. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/workers/__init__.py +0 -0
  277. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/workers/consumer.py +0 -0
  278. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/workers/rabbitmq_worker.py +0 -0
  279. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/infrastructure/workers/runner.py +0 -0
  280. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/py.typed +0 -0
  281. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/sql.py +0 -0
  282. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/sql.pyi +0 -0
  283. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/testing/__init__.py +0 -0
  284. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/testing/fakes.py +0 -0
  285. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/testing/fixtures.py +0 -0
  286. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/testing/helpers.py +0 -0
  287. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/testing/repositories.py +0 -0
  288. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore/types.py +0 -0
  289. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore.egg-info/dependency_links.txt +0 -0
  290. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore.egg-info/entry_points.txt +0 -0
  291. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore.egg-info/requires.txt +0 -0
  292. {hexcore-11.2.0 → hexcore-11.3.0}/hexcore.egg-info/top_level.txt +0 -0
  293. {hexcore-11.2.0 → hexcore-11.3.0}/setup.cfg +0 -0
  294. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_alembic_metadata_completeness.py +0 -0
  295. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_anclas_del_repo.py +0 -0
  296. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_app_and_lifespan.py +0 -0
  297. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_cqrs_providers.py +0 -0
  298. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_exception_handlers.py +0 -0
  299. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_health.py +0 -0
  300. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_middlewares_and_routing.py +0 -0
  301. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_rate_limit.py +0 -0
  302. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_api_streaming.py +0 -0
  303. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_basic.py +0 -0
  304. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_beanie_query_utils.py +0 -0
  305. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_celery_event_loop.py +0 -0
  306. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_config_cors_security.py +0 -0
  307. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_config_loading.py +0 -0
  308. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_consumer_optional_event_bus.py +0 -0
  309. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_cqrs.py +0 -0
  310. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_cqrs_envelope.py +0 -0
  311. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_cqrs_factory.py +0 -0
  312. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_cqrs_middlewares.py +0 -0
  313. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_cron_sql.py +0 -0
  314. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_10_4_hardening.py +0 -0
  315. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_authorization_engine.py +0 -0
  316. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_authorization_http.py +0 -0
  317. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_backend_neutrality.py +0 -0
  318. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_beanie.py +0 -0
  319. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_cli.py +0 -0
  320. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_container.py +0 -0
  321. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_context.py +0 -0
  322. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_custom_plugin.py +0 -0
  323. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_drbac_conditions.py +0 -0
  324. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_drbac_pdp.py +0 -0
  325. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_drbac_router.py +0 -0
  326. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_drbac_service.py +0 -0
  327. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_envelope.py +0 -0
  328. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_facade_and_events.py +0 -0
  329. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_flows.py +0 -0
  330. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_hashing.py +0 -0
  331. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_impersonate.py +0 -0
  332. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_magic_link.py +0 -0
  333. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_models.py +0 -0
  334. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_oauth.py +0 -0
  335. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_openapi.py +0 -0
  336. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_organization.py +0 -0
  337. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_passkey.py +0 -0
  338. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_permissions.py +0 -0
  339. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_plugin_decoupling.py +0 -0
  340. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_plugins.py +0 -0
  341. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_principals.py +0 -0
  342. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_production_defaults.py +0 -0
  343. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_rbac.py +0 -0
  344. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_rbac_service.py +0 -0
  345. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_repositories.py +0 -0
  346. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_revocation.py +0 -0
  347. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_schema_registration.py +0 -0
  348. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_storage_selection.py +0 -0
  349. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_tokens.py +0 -0
  350. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_two_factor.py +0 -0
  351. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_username.py +0 -0
  352. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_darwin_value_objects.py +0 -0
  353. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_deprecations.py +0 -0
  354. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_documentation_examples.py +0 -0
  355. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_domain_base_contract.py +0 -0
  356. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_domain_service_query.py +0 -0
  357. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_dotted_resolution.py +0 -0
  358. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_event_dispatch_mro.py +0 -0
  359. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_aggregate.py +0 -0
  360. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_beanie.py +0 -0
  361. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_config.py +0 -0
  362. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_ports.py +0 -0
  363. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_projections.py +0 -0
  364. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_redis.py +0 -0
  365. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_relay.py +0 -0
  366. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_repository.py +0 -0
  367. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_snapshot_checkpoint.py +0 -0
  368. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_sql_schema.py +0 -0
  369. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_store_contract.py +0 -0
  370. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_eventsourcing_uow_integration.py +0 -0
  371. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_facades.py +0 -0
  372. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_handler_registry.py +0 -0
  373. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_infrastructure_query_path.py +0 -0
  374. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_lock_error_policy.py +0 -0
  375. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_optional_dependencies.py +0 -0
  376. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_packaging.py +0 -0
  377. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_postgres_bus.py +0 -0
  378. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_postgres_lock.py +0 -0
  379. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_query_cursor_and_endpoint.py +0 -0
  380. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_query_field_validation.py +0 -0
  381. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_rabbitmq_bus.py +0 -0
  382. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_redis_bus.py +0 -0
  383. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_redis_lock.py +0 -0
  384. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_repositories_utils.py +0 -0
  385. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_scheduler.py +0 -0
  386. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_scheduler_catchup.py +0 -0
  387. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_smart_routing.py +0 -0
  388. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_sql_naming_convention.py +0 -0
  389. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_sql_session_layer.py +0 -0
  390. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_task_queues_adapters.py +0 -0
  391. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_testing_kit.py +0 -0
  392. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_testing_utilities.py +0 -0
  393. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_typing_gate.py +0 -0
  394. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_uow_session_regression.py +0 -0
  395. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_use_cases_query.py +0 -0
  396. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_worker_bus_integration.py +0 -0
  397. {hexcore-11.2.0 → hexcore-11.3.0}/tests/test_worker_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 11.2.0
3
+ Version: 11.3.0
4
4
  Summary: Reusable core for Python projects with hexagonal architecture, DDD, CQRS and background tasks. Ships the abstractions and the infrastructure: SQL session layer, FastAPI factories, worker runner, dynamic cron and identity.
5
5
  Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
6
6
  License-Expression: MIT
@@ -78,6 +78,26 @@ class ServerConfig(BaseModel):
78
78
  allow_methods: list[str] = ["*"]
79
79
  allow_headers: list[str] = ["*"]
80
80
 
81
+ # CORS para una SPA en otro origen con la cookie de sesión. Los tres van con el prefijo
82
+ # `cors_` porque `max_age` y `expose_headers` a secas, en una config que también tiene
83
+ # cookies y TTLs, se leen como otra cosa.
84
+ #
85
+ # `cors_origin_predicate` cubre los orígenes que `allow_origins` no puede enumerar —un
86
+ # subdominio por tenant creado en caliente—, igual que
87
+ # `IdentityConfig.trusted_origin_predicate` hace con el CSRF. **Se suma** a `allow_origins`,
88
+ # no lo reemplaza. Es síncrono, recibe el origen tal cual llega en el header `Origin`
89
+ # (`"https://a.ejemplo.com"`) y si lanza se trata como «no confiable».
90
+ #
91
+ # `cors_expose_headers`: las cabeceras de la respuesta que el JS puede leer entre orígenes.
92
+ # Sin declararlas, el navegador sólo deja leer las «seguras» (`Content-Type`, etc.).
93
+ #
94
+ # `cors_max_age`: cuántos segundos el navegador cachea el preflight. Sin caché, toda
95
+ # escritura JSON con un header propio (`X-CSRF-Token`) cuesta un `OPTIONS` previo. 600 es el
96
+ # default de Starlette; Chrome topea en 7200.
97
+ cors_origin_predicate: t.Callable[[str], bool] | None = None
98
+ cors_expose_headers: list[str] = Field(default_factory=list)
99
+ cors_max_age: int = Field(default=600, ge=0)
100
+
81
101
  # caching
82
102
  cache_backend: ICache = (
83
103
  MemoryCache()
@@ -171,6 +191,9 @@ class ServerConfig(BaseModel):
171
191
  1. Si no pasaste `allow_origins`, se completa: en `debug` queda `["*"]` (comodidad
172
192
  de desarrollo, que era la intención original), y fuera de `debug` queda
173
193
  `["http://localhost:<port>"]`. Un valor explícito —incluso `[]`— se respeta.
194
+ **Con un `cors_origin_predicate` declarado queda `[]`**: el predicado es la lista, y
195
+ derivar `["*"]` bajaría `allow_credentials` a `False` en silencio y las cookies
196
+ dejarían de funcionar sin que nadie lo pidiera.
174
197
  2. **`"*"` con `allow_credentials=True` nunca es válido, y el invariante vale
175
198
  siempre, no sólo fuera de `debug`.** Es que `debug` viene en `True` por
176
199
  defecto, así que condicionarlo al entorno dejaba la combinación peligrosa
@@ -194,9 +217,12 @@ class ServerConfig(BaseModel):
194
217
  lo_pidio_el_usuario = "allow_origins" in self.model_fields_set
195
218
 
196
219
  if not lo_pidio_el_usuario and not self.allow_origins:
197
- self.allow_origins = (
198
- ["*"] if self.debug else [f"http://localhost:{self.port}"]
199
- )
220
+ if self.cors_origin_predicate is not None:
221
+ self.allow_origins = []
222
+ else:
223
+ self.allow_origins = (
224
+ ["*"] if self.debug else [f"http://localhost:{self.port}"]
225
+ )
200
226
 
201
227
  if "*" not in self.allow_origins or not self.allow_credentials:
202
228
  return self
@@ -31,6 +31,7 @@ from hexcore.darwin.infrastructure.api.dependencies import (
31
31
  provide_auth,
32
32
  require_not_impersonated,
33
33
  )
34
+ from hexcore.darwin.infrastructure.api.middlewares import CSRF_HEADER
34
35
 
35
36
  if t.TYPE_CHECKING:
36
37
  from hexcore.darwin.application.config import IdentityConfig
@@ -42,6 +43,7 @@ __all__ = [
42
43
  "VerifyEmailRequest",
43
44
  "SignInRequest",
44
45
  "SessionResponse",
46
+ "CsrfTokenResponse",
45
47
  "MeResponse",
46
48
  "SignedOutResponse",
47
49
  "RevokedResponse",
@@ -122,6 +124,17 @@ class SignInRequest(BaseModel):
122
124
  return t.cast(str, self.identifier or self.email or self.username)
123
125
 
124
126
 
127
+ class CsrfTokenResponse(BaseModel):
128
+ """
129
+ La respuesta de `GET /auth/csrf`: el valor anti-CSRF de la sesión en curso.
130
+
131
+ Es el mismo que trae la cookie de CSRF —`HMAC(secreto, sid)`, ver `derive_csrf_token`— y
132
+ que las respuestas que la emiten repiten en la cabecera `X-CSRF-Token`.
133
+ """
134
+
135
+ csrf_token: str
136
+
137
+
125
138
  class SessionResponse(BaseModel):
126
139
  """
127
140
  La respuesta de un sign-in o un refresh.
@@ -212,6 +225,14 @@ def emit_tokens(
212
225
  a propósito: el cliente tiene que poder leerla para devolverla en el header
213
226
  `X-CSRF-Token`. Que sea legible es lo que obliga a que su valor sea derivado del `sid` y
214
227
  no aleatorio — ver `derive_csrf_token`.
228
+
229
+ **Además va en la cabecera de la respuesta** (`X-CSRF-Token`, la misma que el cliente
230
+ devuelve en cada escritura). Una SPA en otro origen que la API —`app.ejemplo.com` contra
231
+ `api.ejemplo.com`— no puede leer esa cookie con `document.cookie`, porque pertenece al host
232
+ de la API: sin la cabecera no tiene de dónde sacar el valor, y toda escritura responde 403 sin
233
+ que nada apunte a la causa. La cabecera es legible cross-origin sólo si el CORS la expone
234
+ (`create_app` lo hace cuando `AppFeatures(csrf=True)`), o sea que no se la regala a un origen
235
+ que el CORS no permite; y el valor no sirve sin la cookie de sesión que el navegador ya tiene.
215
236
  """
216
237
  transport.emit(response, tokens)
217
238
 
@@ -234,15 +255,17 @@ def emit_tokens(
234
255
  #
235
256
  # El valor es un HMAC del `sid`, así que es válido mientras viva la sesión: alinearlo con
236
257
  # el refresh es lo coherente, y es el mismo número que la cookie de refresh.
258
+ valor = derive_csrf_token(str(tokens.session_id), clave.get_secret_value())
237
259
  response.set_cookie(
238
260
  config.cookies.name_for("csrf"),
239
- derive_csrf_token(str(tokens.session_id), clave.get_secret_value()),
261
+ valor,
240
262
  max_age=int(config.tokens.refresh_ttl.total_seconds()),
241
263
  httponly=False,
242
264
  secure=config.cookies.secure,
243
265
  samesite=config.cookies.same_site,
244
266
  path=config.cookies.path,
245
267
  )
268
+ response.headers[CSRF_HEADER] = valor
246
269
 
247
270
 
248
271
  def session_response_body(
@@ -464,6 +487,44 @@ def build_identity_router(
464
487
  transport.clear(respuesta)
465
488
  return respuesta
466
489
 
490
+ @router.get("/csrf", response_model=CsrfTokenResponse)
491
+ async def csrf(
492
+ response: Response,
493
+ auth: "AuthContext[t.Any]" = Depends(provide_auth),
494
+ ) -> CsrfTokenResponse:
495
+ """
496
+ El valor anti-CSRF de la sesión por cookie en curso.
497
+
498
+ Para el cliente que **no puede leer la cookie de CSRF**: una SPA en otro origen que la
499
+ API (`document.cookie` sólo ve las cookies de su propio host). Lo pide al abrir la
500
+ página con una sesión ya existente y lo guarda **en memoria** — nunca en
501
+ `localStorage`—; el sign-in y el refresh lo entregan además en la cabecera
502
+ `X-CSRF-Token`, así que sólo hace falta acá cuando la página se recarga.
503
+
504
+ Es seguro darlo: sólo un origen que el CORS permita puede leer la respuesta, y el valor
505
+ no sirve sin la cookie de sesión que el navegador ya tiene. Es `HMAC(secreto, sid)`, el
506
+ mismo cálculo que `emit_tokens`.
507
+
508
+ **401 sin sesión por cookie.** Un cliente Bearer no hace CSRF —adjunta su token a
509
+ propósito— y pedirlo es un error de uso, no algo que haya que contestar.
510
+ """
511
+ from hexcore.darwin.application.container import get_identity_container
512
+ from hexcore.darwin.domain.exceptions import UnauthenticatedError
513
+ from hexcore.darwin.infrastructure.hashing import derive_csrf_token
514
+
515
+ config = get_identity_container().config
516
+ session_id = getattr(auth.actor, "session_id", None)
517
+ if auth.transport != "cookie" or session_id is None or config.secret_key is None:
518
+ raise UnauthenticatedError(
519
+ "El token anti-CSRF sólo existe para una sesión por cookie: un cliente Bearer "
520
+ "no hace CSRF."
521
+ )
522
+
523
+ response.headers["Cache-Control"] = "no-store"
524
+ return CsrfTokenResponse(
525
+ csrf_token=derive_csrf_token(str(session_id), config.secret_key.get_secret_value())
526
+ )
527
+
467
528
  @router.get("/me", response_model=MeResponse)
468
529
  async def me(auth: "AuthContext[t.Any]" = Depends(provide_auth)) -> MeResponse:
469
530
  """
@@ -37,6 +37,10 @@ _EXPORTS: dict[str, tuple[str, str]] = {
37
37
  "RequestIDMiddleware",
38
38
  ),
39
39
  "TimingMiddleware": ("hexcore.infrastructure.api.middlewares", "TimingMiddleware"),
40
+ "PredicateCORSMiddleware": (
41
+ "hexcore.infrastructure.api.middlewares",
42
+ "PredicateCORSMiddleware",
43
+ ),
40
44
  "RequestIDLogFilter": (
41
45
  "hexcore.infrastructure.api.middlewares",
42
46
  "RequestIDLogFilter",
@@ -9,7 +9,7 @@
9
9
  #
10
10
  # Existe porque la fachada resuelve sus exports con `__getattr__` y declara
11
11
  # `__all__ = sorted(_EXPORTS)`: las dos son expresiones de runtime, así que sin este stub
12
- # los 48 símbolos de `hexcore.fastapi` tipan `Any`. El runtime no cambia — Python usa
12
+ # los 49 símbolos de `hexcore.fastapi` tipan `Any`. El runtime no cambia — Python usa
13
13
  # el `.py` y el checker usa el `.pyi`, así que la carga perezosa se mantiene.
14
14
 
15
15
 
@@ -42,6 +42,7 @@ from hexcore.infrastructure.api.lifespan import ProcrastinateStep as Procrastina
42
42
  from hexcore.infrastructure.api.lifespan import SqlEngineStep as SqlEngineStep
43
43
  from hexcore.infrastructure.api.lifespan import StartupStep as StartupStep
44
44
  from hexcore.infrastructure.api.lifespan import build_lifespan as build_lifespan
45
+ from hexcore.infrastructure.api.middlewares import PredicateCORSMiddleware as PredicateCORSMiddleware
45
46
  from hexcore.infrastructure.api.middlewares import RequestIDLogFilter as RequestIDLogFilter
46
47
  from hexcore.infrastructure.api.middlewares import RequestIDMiddleware as RequestIDMiddleware
47
48
  from hexcore.infrastructure.api.middlewares import TimingMiddleware as TimingMiddleware
@@ -74,6 +75,7 @@ __all__ = [
74
75
  "EventBusStep",
75
76
  "HealthReport",
76
77
  "HealthRoutes",
78
+ "PredicateCORSMiddleware",
77
79
  "Probe",
78
80
  "ProcrastinateStep",
79
81
  "RequestIDLogFilter",
@@ -16,7 +16,7 @@ from pydantic import BaseModel
16
16
 
17
17
  from .exception_handlers import HeadersFactory, PayloadFactory, register_exception_handlers
18
18
  from .health import Probe, ResponseFactory, register_health_routes
19
- from .middlewares import RequestIDMiddleware, TimingMiddleware
19
+ from .middlewares import PredicateCORSMiddleware, RequestIDMiddleware, TimingMiddleware
20
20
  from .routing import MountableRouter, mount_routers
21
21
 
22
22
  __all__ = ["AppFeatures", "HealthRoutes", "create_app"]
@@ -139,14 +139,6 @@ def create_app(
139
139
  # Los middlewares de Starlette se ejecutan en orden inverso al de registro, así que
140
140
  # el request-id se añade último para que envuelva a todo lo demás y su ContextVar
141
141
  # esté disponible en el resto de la pila.
142
- if resolved_features.cors:
143
- app.add_middleware(
144
- CORSMiddleware,
145
- allow_origins=list(getattr(config, "allow_origins", ["*"])),
146
- allow_credentials=bool(getattr(config, "allow_credentials", True)),
147
- allow_methods=list(getattr(config, "allow_methods", ["*"])),
148
- allow_headers=list(getattr(config, "allow_headers", ["*"])),
149
- )
150
142
  if resolved_features.timing:
151
143
  app.add_middleware(TimingMiddleware)
152
144
 
@@ -164,6 +156,39 @@ def create_app(
164
156
 
165
157
  app.add_middleware(AuthContextMiddleware)
166
158
 
159
+ # El CORS va **después** de los de Darwin —o sea, por fuera de ellos— y antes del
160
+ # request-id. Un middleware sólo le agrega cabeceras a lo que responde por dentro de él:
161
+ # con el CORS más adentro que el CSRF, el 403 de un `POST` cross-origin sin token salía
162
+ # sin `Access-Control-Allow-Origin`, y el navegador lo convertía en un error de red opaco
163
+ # en vez de dejarle leer a la app *por qué* falló (y reintentar con un token nuevo). Y
164
+ # el preflight se contesta acá, antes de gastar la resolución de la credencial.
165
+ if resolved_features.cors:
166
+ expose_headers = list(getattr(config, "cors_expose_headers", []))
167
+ if resolved_features.csrf:
168
+ # Darwin entrega el token anti-CSRF también en esta cabecera (para la SPA en otro
169
+ # origen, que no puede leer la cookie): sin exponerla, el JS no la ve.
170
+ from hexcore.darwin.infrastructure.api.middlewares import CSRF_HEADER
171
+
172
+ if CSRF_HEADER.lower() not in (h.lower() for h in expose_headers):
173
+ expose_headers.append(CSRF_HEADER)
174
+ cors_kwargs: dict[str, t.Any] = {
175
+ "allow_origins": list(getattr(config, "allow_origins", ["*"])),
176
+ "allow_credentials": bool(getattr(config, "allow_credentials", True)),
177
+ "allow_methods": list(getattr(config, "allow_methods", ["*"])),
178
+ "allow_headers": list(getattr(config, "allow_headers", ["*"])),
179
+ "expose_headers": expose_headers,
180
+ "max_age": int(getattr(config, "cors_max_age", 600)),
181
+ }
182
+ origin_predicate = getattr(config, "cors_origin_predicate", None)
183
+ if origin_predicate is None:
184
+ # Sin predicado, la clase es la de Starlette tal cual: nada cambia para quien no
185
+ # lo pide.
186
+ app.add_middleware(CORSMiddleware, **cors_kwargs)
187
+ else:
188
+ app.add_middleware(
189
+ PredicateCORSMiddleware, origin_predicate=origin_predicate, **cors_kwargs
190
+ )
191
+
167
192
  if resolved_features.request_id:
168
193
  app.add_middleware(RequestIDMiddleware)
169
194
 
@@ -15,12 +15,16 @@ import uuid
15
15
  import warnings
16
16
  from contextvars import ContextVar
17
17
 
18
+ from starlette.datastructures import Headers, MutableHeaders
18
19
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
20
+ from starlette.middleware.cors import CORSMiddleware
19
21
  from starlette.requests import Request
20
22
  from starlette.responses import Response
23
+ from starlette.types import Message, Receive, Scope, Send
21
24
 
22
25
  __all__ = [
23
26
  "REQUEST_ID",
27
+ "PredicateCORSMiddleware",
24
28
  "RequestIDMiddleware",
25
29
  "TimingMiddleware",
26
30
  "RequestIDLogFilter",
@@ -102,6 +106,66 @@ class TimingMiddleware(BaseHTTPMiddleware):
102
106
  return response
103
107
 
104
108
 
109
+ cors_logger = logging.getLogger("hexcore.api.cors")
110
+
111
+
112
+ class PredicateCORSMiddleware(CORSMiddleware):
113
+ """
114
+ `CORSMiddleware` que además acepta los orígenes que un predicado apruebe.
115
+
116
+ `allow_origins` es una lista fija, y un subdominio por tenant creado en caliente no se puede
117
+ enumerar de antemano: sin esto hay que redesplegar por cada alta, o abrir `"*"` — que con
118
+ credenciales no es válido. El predicado se consulta **sólo** cuando el origen no está ya en
119
+ la lista, así que declarar los dos no duplica trabajo.
120
+
121
+ Dos decisiones que no son obvias:
122
+
123
+ * **Falla cerrado.** Un predicado que lanza se trata como «no confiable» y se loguea en
124
+ `hexcore.api.cors`: un chequeo de confianza que explota nunca decide «confío» por default.
125
+ El log importa porque el síntoma —un origen legítimo sin CORS— no apunta a ninguna causa.
126
+ * **`Vary: Origin` siempre.** Con orígenes dinámicos la respuesta depende del `Origin`
127
+ también cuando se rechaza; sin `Vary`, una caché compartida podría servirle a un origen
128
+ aceptado la respuesta que guardó sin `Access-Control-Allow-Origin`. Starlette sólo lo
129
+ agrega cuando el origen es aceptado.
130
+ """
131
+
132
+ def __init__(
133
+ self,
134
+ app: t.Any,
135
+ *,
136
+ origin_predicate: t.Callable[[str], bool],
137
+ **kwargs: t.Any,
138
+ ) -> None:
139
+ super().__init__(app, **kwargs)
140
+ self._origin_predicate = origin_predicate
141
+
142
+ def is_allowed_origin(self, origin: str) -> bool:
143
+ if super().is_allowed_origin(origin):
144
+ return True
145
+ try:
146
+ return bool(self._origin_predicate(origin))
147
+ except Exception:
148
+ cors_logger.exception(
149
+ "cors_origin_predicate lanzó evaluando el origen %r; se lo trata como no "
150
+ "confiable.",
151
+ origin,
152
+ )
153
+ return False
154
+
155
+ async def simple_response(
156
+ self, scope: Scope, receive: Receive, send: Send, request_headers: Headers
157
+ ) -> None:
158
+ async def con_vary(message: Message) -> None:
159
+ if message["type"] == "http.response.start":
160
+ message.setdefault("headers", [])
161
+ headers = MutableHeaders(scope=message)
162
+ if "origin" not in headers.get("vary", "").lower():
163
+ headers.add_vary_header("Origin")
164
+ await send(message)
165
+
166
+ await super().simple_response(scope, receive, con_vary, request_headers)
167
+
168
+
105
169
  class RequestIDLogFilter(logging.Filter):
106
170
  """
107
171
  Inyecta el request-id en cada `LogRecord` como ``record.request_id``.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 11.2.0
3
+ Version: 11.3.0
4
4
  Summary: Reusable core for Python projects with hexagonal architecture, DDD, CQRS and background tasks. Ships the abstractions and the infrastructure: SQL session layer, FastAPI factories, worker runner, dynamic cron and identity.
5
5
  Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
6
6
  License-Expression: MIT
@@ -290,6 +290,7 @@ hexcore/testing/repositories.py
290
290
  tests/test_alembic_metadata_completeness.py
291
291
  tests/test_anclas_del_repo.py
292
292
  tests/test_api_app_and_lifespan.py
293
+ tests/test_api_cors_dinamico.py
293
294
  tests/test_api_cqrs_providers.py
294
295
  tests/test_api_exception_handlers.py
295
296
  tests/test_api_health.py
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
 
11
11
  [project]
12
12
  name = "hexcore"
13
- version = "11.2.0"
13
+ version = "11.3.0"
14
14
  authors = [
15
15
  { name="David Latosefki (Indroic)", email="indroic@outlook.com" },
16
16
  ]
@@ -0,0 +1,250 @@
1
+ """
2
+ CORS de `create_app`: por fuera de la auth y del CSRF, y con orígenes que no se enumeran.
3
+
4
+ Dos problemas del mismo caso de uso —una SPA en otro origen que le habla a la API con la cookie
5
+ de sesión—, y los dos fallan **en silencio**:
6
+
7
+ 1. `create_app` registraba el CORS **primero**, o sea que quedaba *por dentro* de
8
+ `AuthContextMiddleware` y de `CsrfMiddleware`. Un 403 de CSRF salía sin
9
+ `Access-Control-Allow-Origin` y el navegador lo convertía en un error de red opaco: la app
10
+ no podía leer *por qué* falló (ni reintentar con un token nuevo). El orden se prueba acá; que
11
+ el 403 real lleve las cabeceras, en `test_darwin_http.py`.
12
+ 2. `allow_origins` es una lista fija. Con un subdominio por tenant creado en caliente hay que
13
+ redesplegar por cada alta, o abrir `"*"` —que con credenciales no es válido—. Un
14
+ `cors_origin_predicate` cubre lo que la lista no enumera, igual que
15
+ `IdentityConfig.trusted_origin_predicate` hace con el CSRF.
16
+
17
+ Además, sin `cors_max_age` cada escritura JSON con un header propio (`X-CSRF-Token`) cuesta un
18
+ `OPTIONS` previo sin caché, y sin `cors_expose_headers` el JS no puede leer las cabeceras de la
19
+ respuesta que la app le quiere entregar.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ import warnings
25
+
26
+ import pytest
27
+
28
+ pytest.importorskip("fastapi")
29
+ pytest.importorskip("httpx")
30
+
31
+ from fastapi.testclient import TestClient # noqa: E402
32
+ from pydantic import ValidationError # noqa: E402
33
+ from starlette.middleware.cors import CORSMiddleware # noqa: E402
34
+
35
+ from hexcore.config import ServerConfig # noqa: E402
36
+ from hexcore.fastapi import AppFeatures, create_app # noqa: E402
37
+ from hexcore.infrastructure.api.middlewares import PredicateCORSMiddleware # noqa: E402
38
+
39
+ TENANT = "https://a.tenant.test"
40
+ AJENO = "https://evil.example"
41
+
42
+
43
+ def _es_de_un_tenant(origen: str) -> bool:
44
+ return origen.endswith(".tenant.test")
45
+
46
+
47
+ def _app(monkeypatch: pytest.MonkeyPatch, **campos):
48
+ """Una app con una `ServerConfig` propia (el `_config()` de `create_app` apunta a ella)."""
49
+ config = ServerConfig(**campos)
50
+ monkeypatch.setattr("hexcore.infrastructure.api.app._config", lambda: config)
51
+ return create_app(), config
52
+
53
+
54
+ def _nombres(app) -> list[str]:
55
+ """De **afuera hacia adentro**: `user_middleware[0]` es el primero que ve el request."""
56
+ return [m.cls.__name__ for m in app.user_middleware]
57
+
58
+
59
+ # ── 1. Orden ───────────────────────────────────────────────────────────────────
60
+ def test_el_cors_envuelve_a_la_auth_y_al_csrf(monkeypatch):
61
+ """
62
+ Un middleware de Starlette sólo puede agregarle cabeceras a lo que responde **por dentro**
63
+ de él. Si el CORS está adentro del CSRF, el 403 del CSRF no las lleva.
64
+ """
65
+ config = ServerConfig(allow_origins=[TENANT])
66
+ monkeypatch.setattr("hexcore.infrastructure.api.app._config", lambda: config)
67
+
68
+ app = create_app(features=AppFeatures(auth_context=True, csrf=True))
69
+ nombres = _nombres(app)
70
+
71
+ assert nombres.index("CORSMiddleware") < nombres.index("AuthContextMiddleware")
72
+ assert nombres.index("CORSMiddleware") < nombres.index("CsrfMiddleware")
73
+
74
+
75
+ def test_el_request_id_sigue_siendo_el_mas_externo(monkeypatch):
76
+ """
77
+ El request-id envuelve a todo lo demás para que su `ContextVar` esté disponible en la pila
78
+ entera, y el 403 del CSRF —que ahora sale por dentro del CORS— lo sigue llevando.
79
+ """
80
+ config = ServerConfig(allow_origins=[TENANT])
81
+ monkeypatch.setattr("hexcore.infrastructure.api.app._config", lambda: config)
82
+
83
+ app = create_app(features=AppFeatures(auth_context=True, csrf=True))
84
+
85
+ assert _nombres(app)[0] == "RequestIDMiddleware"
86
+
87
+
88
+ def test_sin_predicado_el_middleware_es_el_de_starlette_tal_cual(monkeypatch):
89
+ """Quien no pide un predicado no cambia de clase: el comportamiento es el de siempre."""
90
+ app, _ = _app(monkeypatch, allow_origins=[TENANT])
91
+
92
+ clases = [m.cls for m in app.user_middleware]
93
+
94
+ assert CORSMiddleware in clases
95
+ assert PredicateCORSMiddleware not in clases
96
+
97
+
98
+ # ── 2. cors_origin_predicate ───────────────────────────────────────────────────
99
+ def test_un_origen_que_el_predicado_acepta_recibe_cors_con_credenciales(monkeypatch):
100
+ app, _ = _app(monkeypatch, allow_origins=[], cors_origin_predicate=_es_de_un_tenant)
101
+
102
+ with TestClient(app) as client:
103
+ r = client.get("/health", headers={"Origin": TENANT})
104
+
105
+ assert r.headers["access-control-allow-origin"] == TENANT
106
+ assert r.headers["access-control-allow-credentials"] == "true"
107
+
108
+
109
+ def test_un_origen_que_el_predicado_rechaza_no_recibe_cors(monkeypatch):
110
+ app, _ = _app(monkeypatch, allow_origins=[], cors_origin_predicate=_es_de_un_tenant)
111
+
112
+ with TestClient(app) as client:
113
+ r = client.get("/health", headers={"Origin": AJENO})
114
+
115
+ assert "access-control-allow-origin" not in r.headers
116
+
117
+
118
+ def test_el_preflight_de_un_origen_aceptado_pasa_y_el_de_uno_rechazado_no(monkeypatch):
119
+ app, _ = _app(monkeypatch, allow_origins=[], cors_origin_predicate=_es_de_un_tenant)
120
+ pedido = {
121
+ "Access-Control-Request-Method": "POST",
122
+ "Access-Control-Request-Headers": "content-type, x-csrf-token",
123
+ }
124
+
125
+ with TestClient(app) as client:
126
+ bueno = client.options("/health", headers={"Origin": TENANT, **pedido})
127
+ malo = client.options("/health", headers={"Origin": AJENO, **pedido})
128
+
129
+ assert bueno.status_code == 200
130
+ assert bueno.headers["access-control-allow-origin"] == TENANT
131
+ assert malo.status_code == 400
132
+ assert "access-control-allow-origin" not in malo.headers
133
+
134
+
135
+ def test_la_lista_fija_y_el_predicado_se_suman(monkeypatch):
136
+ """La lista sigue mandando para lo que enumera; el predicado cubre el resto."""
137
+ fijo = "https://portal.example"
138
+ app, _ = _app(monkeypatch, allow_origins=[fijo], cors_origin_predicate=_es_de_un_tenant)
139
+
140
+ with TestClient(app) as client:
141
+ de_la_lista = client.get("/health", headers={"Origin": fijo})
142
+ del_predicado = client.get("/health", headers={"Origin": TENANT})
143
+ ajeno = client.get("/health", headers={"Origin": AJENO})
144
+
145
+ assert de_la_lista.headers["access-control-allow-origin"] == fijo
146
+ assert del_predicado.headers["access-control-allow-origin"] == TENANT
147
+ assert "access-control-allow-origin" not in ajeno.headers
148
+
149
+
150
+ def test_un_predicado_que_lanza_se_trata_como_no_confiable(monkeypatch, caplog):
151
+ """
152
+ Falla cerrado, igual que `trusted_origin_predicate` y que un `AuthorizationProvider` que
153
+ revienta: un chequeo de confianza que explota nunca decide «confío» por default. Y se
154
+ loguea, porque el síntoma —un origen legítimo sin CORS— si no, no se diagnostica.
155
+ """
156
+
157
+ def explota(origen: str) -> bool:
158
+ raise RuntimeError("la base de tenants no responde")
159
+
160
+ app, _ = _app(monkeypatch, allow_origins=[], cors_origin_predicate=explota)
161
+
162
+ with caplog.at_level(logging.ERROR, logger="hexcore.api.cors"):
163
+ with TestClient(app) as client:
164
+ r = client.get("/health", headers={"Origin": TENANT})
165
+
166
+ assert r.status_code == 200
167
+ assert "access-control-allow-origin" not in r.headers
168
+ assert any("cors_origin_predicate" in m for m in caplog.messages)
169
+
170
+
171
+ def test_la_respuesta_a_un_origen_rechazado_igual_declara_vary_origin(monkeypatch):
172
+ """
173
+ Con orígenes dinámicos la respuesta depende del `Origin` **también cuando se rechaza**. Sin
174
+ `Vary: Origin`, una caché compartida podría servirle a un origen aceptado la respuesta que
175
+ guardó sin `Access-Control-Allow-Origin`.
176
+ """
177
+ app, _ = _app(monkeypatch, allow_origins=[], cors_origin_predicate=_es_de_un_tenant)
178
+
179
+ with TestClient(app) as client:
180
+ r = client.get("/health", headers={"Origin": AJENO})
181
+
182
+ assert "Origin" in r.headers["vary"]
183
+
184
+
185
+ def test_con_predicado_y_sin_allow_origins_no_se_deriva_el_comodin(monkeypatch):
186
+ """
187
+ El default de `allow_origins` en `debug` es `["*"]`, y con credenciales eso **baja
188
+ `allow_credentials` a `False` en silencio** (con un warning). Con un predicado declarado, el
189
+ predicado es la lista: derivar `["*"]` apagaría las cookies sin que nadie lo pidiera.
190
+ """
191
+ with warnings.catch_warnings(record=True) as avisos:
192
+ warnings.simplefilter("always")
193
+ config = ServerConfig(cors_origin_predicate=_es_de_un_tenant)
194
+
195
+ assert config.allow_origins == []
196
+ assert config.allow_credentials is True
197
+ assert not [a for a in avisos if "allow_credentials" in str(a.message)]
198
+
199
+
200
+ def test_un_comodin_explicito_sigue_bajando_las_credenciales():
201
+ """Lo que ya no era válido sigue sin serlo: el predicado no relaja esa regla."""
202
+ with pytest.warns(UserWarning, match="allow_credentials"):
203
+ config = ServerConfig(allow_origins=["*"], cors_origin_predicate=_es_de_un_tenant)
204
+
205
+ assert config.allow_credentials is False
206
+
207
+
208
+ # ── 3. cors_max_age y cors_expose_headers ──────────────────────────────────────
209
+ def test_cors_max_age_llega_al_preflight(monkeypatch):
210
+ app, _ = _app(monkeypatch, allow_origins=[TENANT], cors_max_age=7200)
211
+
212
+ with TestClient(app) as client:
213
+ r = client.options(
214
+ "/health",
215
+ headers={"Origin": TENANT, "Access-Control-Request-Method": "POST"},
216
+ )
217
+
218
+ assert r.headers["access-control-max-age"] == "7200"
219
+
220
+
221
+ def test_el_max_age_por_defecto_es_el_de_starlette(monkeypatch):
222
+ """Quien no lo declara no ve cambiar nada: 600 s, el default de `CORSMiddleware`."""
223
+ app, config = _app(monkeypatch, allow_origins=[TENANT])
224
+
225
+ with TestClient(app) as client:
226
+ r = client.options(
227
+ "/health",
228
+ headers={"Origin": TENANT, "Access-Control-Request-Method": "GET"},
229
+ )
230
+
231
+ assert config.cors_max_age == 600
232
+ assert r.headers["access-control-max-age"] == "600"
233
+
234
+
235
+ def test_un_max_age_negativo_no_es_valido():
236
+ with pytest.raises(ValidationError):
237
+ ServerConfig(cors_max_age=-1)
238
+
239
+
240
+ def test_cors_expose_headers_se_declara_en_la_respuesta(monkeypatch):
241
+ app, _ = _app(
242
+ monkeypatch,
243
+ allow_origins=[TENANT],
244
+ cors_expose_headers=["X-Request-ID", "Retry-After"],
245
+ )
246
+
247
+ with TestClient(app) as client:
248
+ r = client.get("/health", headers={"Origin": TENANT})
249
+
250
+ assert r.headers["access-control-expose-headers"] == "X-Request-ID, Retry-After"