hexcore 9.0.0__tar.gz → 9.2.1__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.
- {hexcore-9.0.0/hexcore.egg-info → hexcore-9.2.1}/PKG-INFO +4 -4
- {hexcore-9.0.0 → hexcore-9.2.1}/README.md +3 -3
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/plugins.py +12 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/plugins.py +16 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/dependencies.py +25 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/routers.py +44 -15
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/router.py +41 -3
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/magic_link/router.py +34 -8
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/router.py +23 -2
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/router.py +18 -6
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/router.py +31 -3
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/__init__.py +12 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/router.py +3 -1
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/app.py +36 -13
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/exception_handlers.py +40 -3
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/_sqlalchemy_impl.py +2 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.py +8 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/__init__.py +54 -17
- {hexcore-9.0.0 → hexcore-9.2.1/hexcore.egg-info}/PKG-INFO +4 -4
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/SOURCES.txt +2 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/pyproject.toml +28 -4
- hexcore-9.2.1/tests/test_anclas_del_repo.py +119 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_exception_handlers.py +89 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_backend_neutrality.py +2 -3
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_cli.py +3 -8
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_models.py +2 -4
- hexcore-9.2.1/tests/test_darwin_openapi.py +125 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_plugin_decoupling.py +4 -2
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_two_factor.py +75 -6
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_deprecations.py +2 -7
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_documentation_examples.py +1 -2
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_uow_integration.py +101 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_packaging.py +9 -9
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_typing_gate.py +2 -1
- {hexcore-9.0.0 → hexcore-9.2.1}/LICENSE +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/MANIFEST.in +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/__main__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/_deprecation.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/adapters.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/config.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/factory.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/in_memory_buses.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/pipeline.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/registry.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/scheduler.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/base.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/cursor.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/errors.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/query.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/config.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/factory.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/projector.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/relay.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/use_cases/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/use_cases/base.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/use_cases/query.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/capabilities.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/config.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/cqrs.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/cqrs.pyi +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/__init__.pyi +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/commands.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/config.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/container.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/hooks.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/services.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/context.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/entities.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/events.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/exceptions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/permissions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/ports.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/value_objects.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/middlewares.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/cli.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/clock.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/envelope.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/hashing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/keys.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/lifespan.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/documents.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/repositories.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/schema.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/selection.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/models.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/models_mixins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/repositories.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/schema.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/revocation.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/secretbox.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/tokens.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/transports.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/commands.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/domain.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/service.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/magic_link/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/magic_link/commands.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/domain.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/http.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/beanie/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/beanie/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/models.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/models_mixins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/providers.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/service.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/domain.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/beanie/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/beanie/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/models.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/models_mixins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/service.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/domain.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/beanie/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/beanie/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/models.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/models_mixins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/service.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/webauthn_adapter.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/storage.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/commands.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/crypto.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/domain.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/beanie/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/beanie/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/models.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/models_mixins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/service.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/totp.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/fakes.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/fixtures.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/helpers.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/auth/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/auth/permissions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/auth/value_objects.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/base.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/buses.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/commands.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/context.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/cron.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/decorators.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/dispatch.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/envelope.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/exceptions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/handlers.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/middleware.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/queries.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/resolution.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/serializer.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/task_queues.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/events.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/aggregate.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/exceptions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/projections.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/snapshots.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/store.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/stored.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/exceptions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/repositories.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/services.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/uow.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/eventsourcing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/eventsourcing.pyi +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/fastapi.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/fastapi.pyi +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/cqrs.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/eventsourcing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/health.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/lifespan.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/middlewares.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/rate_limit.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/routing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/streaming.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/utils.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/cache_backends/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/cache_backends/memory.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/cache_backends/redis.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cli.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/cron_sql.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/middlewares.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/postgres_bus.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/postgres_lock.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/procrastinate.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/pydantic_serializer.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/rabbitmq.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/redis_bus.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/redis_lock.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/events/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/events/events_backends/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/events/events_backends/memory.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/beanie_documents.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/beanie_store.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/memory.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/redis_store.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/sqlalchemy_models.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/sqlalchemy_models_mixins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/sqlalchemy_store.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/_beanie_impl.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/base.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/decorators.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/implementations.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/beanie/utils.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/utils.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/task_queues/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/task_queues/celery_adapter.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/task_queues/procrastinate_adapter.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/decorators.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/helpers.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/scopes.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/consumer.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/rabbitmq_worker.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/runner.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/py.typed +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/sql.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/sql.pyi +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/__init__.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/fakes.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/fixtures.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/helpers.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/repositories.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/types.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/dependency_links.txt +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/entry_points.txt +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/requires.txt +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/top_level.txt +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/setup.cfg +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_alembic_metadata_completeness.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_app_and_lifespan.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_cqrs_providers.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_health.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_middlewares_and_routing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_rate_limit.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_streaming.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_basic.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_beanie_query_utils.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_celery_event_loop.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_config_cors_security.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_config_loading.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_consumer_optional_event_bus.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs_envelope.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs_factory.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs_middlewares.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cron_sql.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_beanie.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_container.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_context.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_custom_plugin.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_envelope.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_facade_and_events.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_flows.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_hashing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_http.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_impersonate.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_magic_link.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_oauth.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_organization.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_passkey.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_permissions.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_plugins.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_repositories.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_revocation.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_schema_registration.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_storage_selection.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_tokens.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_value_objects.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_domain_base_contract.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_domain_service_query.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_dotted_resolution.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_event_dispatch_mro.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_aggregate.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_beanie.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_config.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_ports.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_projections.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_redis.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_relay.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_repository.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_snapshot_checkpoint.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_sql_schema.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_store_contract.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_facades.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_handler_registry.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_infrastructure_query_path.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_lock_error_policy.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_optional_dependencies.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_postgres_bus.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_postgres_lock.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_query_cursor_and_endpoint.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_query_field_validation.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_rabbitmq_bus.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_redis_bus.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_redis_lock.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_repositories_utils.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_scheduler.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_scheduler_catchup.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_smart_routing.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_sql_naming_convention.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_sql_session_layer.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_task_queues_adapters.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_testing_kit.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_testing_utilities.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_uow_session_regression.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_use_cases_query.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_worker_bus_integration.py +0 -0
- {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_worker_runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexcore
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.2.1
|
|
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
|
|
@@ -335,6 +335,6 @@ There is a set of skills for extending HexCore in VS Code and compatible environ
|
|
|
335
335
|
|
|
336
336
|
- [docs/](./docs/) — the complete documentation, in English and Spanish.
|
|
337
337
|
- [docs/ARCHITECTURE_TYPING.md](./docs/ARCHITECTURE_TYPING.md) — type system and stubs.
|
|
338
|
-
- [CHANGELOG.md](
|
|
339
|
-
- [CONTRIBUTING.md](
|
|
340
|
-
- [SECURITY.md](
|
|
338
|
+
- [CHANGELOG.md](../../CHANGELOG.md) — change history.
|
|
339
|
+
- [CONTRIBUTING.md](../../CONTRIBUTING.md) — collaboration guidelines.
|
|
340
|
+
- [SECURITY.md](../../SECURITY.md) — security policy.
|
|
@@ -260,6 +260,6 @@ There is a set of skills for extending HexCore in VS Code and compatible environ
|
|
|
260
260
|
|
|
261
261
|
- [docs/](./docs/) — the complete documentation, in English and Spanish.
|
|
262
262
|
- [docs/ARCHITECTURE_TYPING.md](./docs/ARCHITECTURE_TYPING.md) — type system and stubs.
|
|
263
|
-
- [CHANGELOG.md](
|
|
264
|
-
- [CONTRIBUTING.md](
|
|
265
|
-
- [SECURITY.md](
|
|
263
|
+
- [CHANGELOG.md](../../CHANGELOG.md) — change history.
|
|
264
|
+
- [CONTRIBUTING.md](../../CONTRIBUTING.md) — collaboration guidelines.
|
|
265
|
+
- [SECURITY.md](../../SECURITY.md) — security policy.
|
|
@@ -333,6 +333,18 @@ class PluginRegistry:
|
|
|
333
333
|
acumulado.update(plugin.exception_status_map())
|
|
334
334
|
return acumulado
|
|
335
335
|
|
|
336
|
+
def exception_payload(self, exc: Exception) -> dict[str, t.Any]:
|
|
337
|
+
"""
|
|
338
|
+
El payload extra combinado que los plugins agregan al body de `exc`.
|
|
339
|
+
|
|
340
|
+
Espejo de `exception_status_map()`: el del último plugin gana, y `create_app`
|
|
341
|
+
mergea esto debajo del identity-level y del consumidor.
|
|
342
|
+
"""
|
|
343
|
+
acumulado: dict[str, t.Any] = {}
|
|
344
|
+
for plugin in self.plugins:
|
|
345
|
+
acumulado.update(plugin.exception_payload(exc))
|
|
346
|
+
return acumulado
|
|
347
|
+
|
|
336
348
|
def register_handlers(self, registry: t.Any) -> t.Any:
|
|
337
349
|
for plugin in self.plugins:
|
|
338
350
|
plugin.register_handlers(registry)
|
|
@@ -237,6 +237,22 @@ class DarwinPlugin(abc.ABC):
|
|
|
237
237
|
"""
|
|
238
238
|
return {}
|
|
239
239
|
|
|
240
|
+
def exception_payload(self, exc: Exception) -> t.Mapping[str, t.Any]:
|
|
241
|
+
"""
|
|
242
|
+
Claves extra para el **cuerpo** de la excepción, si el plugin las tiene.
|
|
243
|
+
|
|
244
|
+
Espejo de `exception_status_map()` pero para el body en vez del status: hay
|
|
245
|
+
excepciones que llevan datos que el cliente necesita para completar el flujo — el
|
|
246
|
+
`challenge` de `TwoFactorRequiredError`, que el segundo paso del login exige de
|
|
247
|
+
vuelta en `POST /auth/2fa/challenge` — y que no caben en `detail` sin obligar al
|
|
248
|
+
cliente a parsear texto.
|
|
249
|
+
|
|
250
|
+
Método **concreto** que devuelve vacío, igual que el resto de los puntos de
|
|
251
|
+
extensión de `DarwinPlugin`: agregar éste no rompe a ningún plugin existente, que
|
|
252
|
+
no tiene por qué aportar nada al body de sus errores.
|
|
253
|
+
"""
|
|
254
|
+
return {}
|
|
255
|
+
|
|
240
256
|
def register_handlers(self, registry: t.Any) -> None:
|
|
241
257
|
"""
|
|
242
258
|
Registra los comandos y queries del plugin en el `HandlerRegistry`.
|
|
@@ -33,6 +33,7 @@ __all__ = [
|
|
|
33
33
|
"require_roles",
|
|
34
34
|
"require_not_impersonated",
|
|
35
35
|
"identity_exception_headers",
|
|
36
|
+
"identity_exception_payload",
|
|
36
37
|
]
|
|
37
38
|
|
|
38
39
|
#: El valor de `WWW-Authenticate` de un 401. RFC 6750 §3.
|
|
@@ -174,3 +175,27 @@ def identity_exception_headers(exc: Exception) -> t.Mapping[str, str]:
|
|
|
174
175
|
if isinstance(exc, AuthenticationError):
|
|
175
176
|
return {"WWW-Authenticate": WWW_AUTHENTICATE}
|
|
176
177
|
return {}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def identity_exception_payload(exc: Exception) -> t.Mapping[str, t.Any]:
|
|
181
|
+
"""
|
|
182
|
+
El payload extra que las excepciones de identidad agregan al cuerpo del error.
|
|
183
|
+
|
|
184
|
+
Se pasa a `create_app(exception_payload=...)`, y `create_app` lo hace sólo cuando
|
|
185
|
+
`AppFeatures.auth_context` está prendido — mismo cableado que `identity_exception_headers`.
|
|
186
|
+
|
|
187
|
+
A diferencia de los headers —genéricos para toda `AuthenticationError`—, qué payload
|
|
188
|
+
corresponde es específico de cada plugin (el `challenge` de `TwoFactorRequiredError`, por
|
|
189
|
+
ejemplo), así que esta función delega en `PluginRegistry.exception_payload()` del
|
|
190
|
+
despliegue en curso en vez de chequear un tipo fijo.
|
|
191
|
+
|
|
192
|
+
Sin un despliegue configurado (`create_app` corrido antes de `configure_identity`, o sin
|
|
193
|
+
Darwin) devuelve vacío en vez de lanzar — mismo trato que `_mapa_de_plugins()` en `app.py`.
|
|
194
|
+
"""
|
|
195
|
+
from hexcore.darwin.application.container import get_identity_container
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
contenedor = get_identity_container()
|
|
199
|
+
except Exception:
|
|
200
|
+
return {}
|
|
201
|
+
return contenedor.plugins.exception_payload(exc)
|
|
@@ -43,6 +43,9 @@ __all__ = [
|
|
|
43
43
|
"SignInRequest",
|
|
44
44
|
"SessionResponse",
|
|
45
45
|
"MeResponse",
|
|
46
|
+
"SignedOutResponse",
|
|
47
|
+
"RevokedResponse",
|
|
48
|
+
"SessionInfo",
|
|
46
49
|
"build_identity_router",
|
|
47
50
|
"emit_tokens",
|
|
48
51
|
"session_response_body",
|
|
@@ -100,6 +103,31 @@ class MeResponse(BaseModel):
|
|
|
100
103
|
scopes: list[str] = Field(default_factory=list)
|
|
101
104
|
|
|
102
105
|
|
|
106
|
+
class SignedOutResponse(BaseModel):
|
|
107
|
+
"""La respuesta de `POST /auth/sign-out`."""
|
|
108
|
+
|
|
109
|
+
signed_out: bool
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class RevokedResponse(BaseModel):
|
|
113
|
+
"""La respuesta de `POST /auth/sign-out-everywhere`: cuántas sesiones se revocaron."""
|
|
114
|
+
|
|
115
|
+
revoked: int
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SessionInfo(BaseModel):
|
|
119
|
+
"""Una fila de `GET /auth/sessions`, para una pantalla de seguridad."""
|
|
120
|
+
|
|
121
|
+
session_id: str
|
|
122
|
+
transport: str
|
|
123
|
+
created_at: str
|
|
124
|
+
expires_at: str
|
|
125
|
+
ip_address: str | None
|
|
126
|
+
user_agent: str | None
|
|
127
|
+
impersonated: bool
|
|
128
|
+
current: bool
|
|
129
|
+
|
|
130
|
+
|
|
103
131
|
# ── Helpers de transporte ─────────────────────────────────────────────────────
|
|
104
132
|
def resolve_transport(request: Request) -> "AbstractTransport":
|
|
105
133
|
"""Dependencia: el transporte de este request, resuelto una sola vez."""
|
|
@@ -241,7 +269,7 @@ def build_identity_router(
|
|
|
241
269
|
usuario = await servicio.verify_email(email=payload.email, code=payload.code)
|
|
242
270
|
return {"email_verified": usuario.email_verified}
|
|
243
271
|
|
|
244
|
-
@router.post("/sign-in", dependencies=limite)
|
|
272
|
+
@router.post("/sign-in", dependencies=limite, response_model=SessionResponse)
|
|
245
273
|
async def sign_in(
|
|
246
274
|
payload: SignInRequest,
|
|
247
275
|
request: Request,
|
|
@@ -270,7 +298,7 @@ def build_identity_router(
|
|
|
270
298
|
emit_tokens(respuesta, tokens, transport, contenedor.config)
|
|
271
299
|
return respuesta
|
|
272
300
|
|
|
273
|
-
@router.post("/refresh")
|
|
301
|
+
@router.post("/refresh", response_model=SessionResponse)
|
|
274
302
|
async def refresh(
|
|
275
303
|
request: Request,
|
|
276
304
|
transport: "AbstractTransport" = Depends(resolve_transport),
|
|
@@ -304,7 +332,7 @@ def build_identity_router(
|
|
|
304
332
|
emit_tokens(respuesta, tokens, transport, contenedor.config)
|
|
305
333
|
return respuesta
|
|
306
334
|
|
|
307
|
-
@router.post("/sign-out")
|
|
335
|
+
@router.post("/sign-out", response_model=SignedOutResponse)
|
|
308
336
|
async def sign_out(
|
|
309
337
|
auth: "AuthContext[t.Any]" = Depends(provide_auth),
|
|
310
338
|
transport: "AbstractTransport" = Depends(resolve_transport),
|
|
@@ -335,6 +363,7 @@ def build_identity_router(
|
|
|
335
363
|
@router.post(
|
|
336
364
|
"/sign-out-everywhere",
|
|
337
365
|
dependencies=[Depends(require_not_impersonated("sign_out_everywhere"))],
|
|
366
|
+
response_model=RevokedResponse,
|
|
338
367
|
)
|
|
339
368
|
async def sign_out_everywhere(
|
|
340
369
|
auth: "AuthContext[t.Any]" = Depends(provide_auth),
|
|
@@ -386,10 +415,10 @@ def build_identity_router(
|
|
|
386
415
|
scopes=sorted(auth.actor.scopes),
|
|
387
416
|
)
|
|
388
417
|
|
|
389
|
-
@router.get("/sessions")
|
|
418
|
+
@router.get("/sessions", response_model=list[SessionInfo])
|
|
390
419
|
async def sessions(
|
|
391
420
|
auth: "AuthContext[t.Any]" = Depends(provide_auth),
|
|
392
|
-
) -> list[
|
|
421
|
+
) -> list[SessionInfo]:
|
|
393
422
|
"""
|
|
394
423
|
Las sesiones vivas del sujeto, para una pantalla de seguridad.
|
|
395
424
|
|
|
@@ -402,16 +431,16 @@ def build_identity_router(
|
|
|
402
431
|
repo = get_identity_container().sessions_repository()
|
|
403
432
|
vivas = await repo.list_active_for_user(t.cast(t.Any, auth.subject_id))
|
|
404
433
|
return [
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
434
|
+
SessionInfo(
|
|
435
|
+
session_id=str(s.id),
|
|
436
|
+
transport=s.transport,
|
|
437
|
+
created_at=s.created_at.isoformat(),
|
|
438
|
+
expires_at=s.expires_at.isoformat(),
|
|
439
|
+
ip_address=s.ip_address,
|
|
440
|
+
user_agent=s.user_agent,
|
|
441
|
+
impersonated=s.is_impersonated,
|
|
442
|
+
current=s.id == getattr(auth.actor, "session_id", None),
|
|
443
|
+
)
|
|
415
444
|
for s in vivas
|
|
416
445
|
]
|
|
417
446
|
|
|
@@ -22,7 +22,15 @@ from fastapi import APIRouter, Depends, Request, Response
|
|
|
22
22
|
from fastapi.responses import JSONResponse
|
|
23
23
|
from pydantic import BaseModel, Field
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
from hexcore.darwin.infrastructure.api.routers import SessionResponse
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"StartImpersonationBody",
|
|
29
|
+
"ImpersonationStatus",
|
|
30
|
+
"ImpersonationStoppedResponse",
|
|
31
|
+
"ImpersonationStartedResponse",
|
|
32
|
+
"build_impersonate_router",
|
|
33
|
+
]
|
|
26
34
|
|
|
27
35
|
|
|
28
36
|
class StartImpersonationBody(BaseModel):
|
|
@@ -44,6 +52,30 @@ class ImpersonationStatus(BaseModel):
|
|
|
44
52
|
expires_at: str | None = None
|
|
45
53
|
|
|
46
54
|
|
|
55
|
+
class ImpersonationStoppedResponse(BaseModel):
|
|
56
|
+
"""La respuesta de `POST /auth/impersonate/stop`."""
|
|
57
|
+
|
|
58
|
+
stopped: bool
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ImpersonationStartedResponse(SessionResponse):
|
|
62
|
+
"""
|
|
63
|
+
La respuesta de `POST /auth/impersonate/{user_id}`.
|
|
64
|
+
|
|
65
|
+
`impersonating` queda **deprecado**: en esta ruta lleva el id del sujeto impersonado como
|
|
66
|
+
string, mientras que en `MeResponse.impersonating` es un `bool` — el mismo nombre significa
|
|
67
|
+
dos cosas distintas según la ruta, y un cliente TypeScript no puede tipar las dos con el
|
|
68
|
+
mismo campo sin mentir. Renombrarlo sería breaking y no lo amerita un major; en su lugar,
|
|
69
|
+
`subject_id` es el mismo valor con un nombre que no colisiona.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
impersonating: str
|
|
73
|
+
subject_id: str
|
|
74
|
+
#: Cuándo vence la ventana de impersonación (no la del access token, que es
|
|
75
|
+
#: `expires_in`, heredado de `SessionResponse`).
|
|
76
|
+
expires_at: str | None = None
|
|
77
|
+
|
|
78
|
+
|
|
47
79
|
def build_impersonate_router(
|
|
48
80
|
*,
|
|
49
81
|
prefix: str = "/auth/impersonate",
|
|
@@ -95,7 +127,7 @@ def build_impersonate_router(
|
|
|
95
127
|
# registro, así que con `/{user_id}` primero, un `POST /auth/impersonate/stop` matchea la
|
|
96
128
|
# ruta paramétrica e intenta parsear `"stop"` como UUID — 422 en vez de terminar la
|
|
97
129
|
# impersonación. Lo encontró un test, no la revisión.
|
|
98
|
-
@router.post("/stop")
|
|
130
|
+
@router.post("/stop", response_model=ImpersonationStoppedResponse)
|
|
99
131
|
async def stop(
|
|
100
132
|
auth: t.Any = Depends(provide_auth),
|
|
101
133
|
transport: t.Any = Depends(_resolve_transport),
|
|
@@ -114,7 +146,9 @@ def build_impersonate_router(
|
|
|
114
146
|
transport.clear(respuesta)
|
|
115
147
|
return respuesta
|
|
116
148
|
|
|
117
|
-
@router.post(
|
|
149
|
+
@router.post(
|
|
150
|
+
"/{user_id}", dependencies=limite, response_model=ImpersonationStartedResponse
|
|
151
|
+
)
|
|
118
152
|
async def start(
|
|
119
153
|
user_id: UUID,
|
|
120
154
|
payload: StartImpersonationBody,
|
|
@@ -149,6 +183,10 @@ def build_impersonate_router(
|
|
|
149
183
|
exclude_none=True
|
|
150
184
|
)
|
|
151
185
|
cuerpo["impersonating"] = str(resultado.subject.id)
|
|
186
|
+
# `subject_id` es el mismo valor que `impersonating`, con un nombre que no colisiona
|
|
187
|
+
# con el `bool` de `MeResponse.impersonating`. Ver el docstring de
|
|
188
|
+
# `ImpersonationStartedResponse`.
|
|
189
|
+
cuerpo["subject_id"] = str(resultado.subject.id)
|
|
152
190
|
cuerpo["expires_at"] = (
|
|
153
191
|
resultado.session.impersonation_expires_at.isoformat()
|
|
154
192
|
if resultado.session.impersonation_expires_at
|
|
@@ -18,7 +18,14 @@ from fastapi import APIRouter, Depends, Request, Response
|
|
|
18
18
|
from fastapi.responses import JSONResponse
|
|
19
19
|
from pydantic import BaseModel
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
from hexcore.darwin.infrastructure.api.routers import SessionResponse
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"RequestMagicLinkBody",
|
|
25
|
+
"ConsumeMagicLinkBody",
|
|
26
|
+
"MagicLinkRequested",
|
|
27
|
+
"build_magic_link_router",
|
|
28
|
+
]
|
|
22
29
|
|
|
23
30
|
|
|
24
31
|
class RequestMagicLinkBody(BaseModel):
|
|
@@ -30,6 +37,18 @@ class ConsumeMagicLinkBody(BaseModel):
|
|
|
30
37
|
token: str
|
|
31
38
|
|
|
32
39
|
|
|
40
|
+
class MagicLinkRequested(BaseModel):
|
|
41
|
+
"""
|
|
42
|
+
La respuesta de `POST /auth/magic-link/request`.
|
|
43
|
+
|
|
44
|
+
`token` sólo viaja en un despliegue que no manda mails de verdad — ver la advertencia del
|
|
45
|
+
docstring de la ruta: en producción no se devuelve, y el campo queda `None`.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
sent: bool
|
|
49
|
+
token: str | None = None
|
|
50
|
+
|
|
51
|
+
|
|
33
52
|
def build_magic_link_router(
|
|
34
53
|
*,
|
|
35
54
|
prefix: str = "/auth/magic-link",
|
|
@@ -58,8 +77,18 @@ def build_magic_link_router(
|
|
|
58
77
|
vida = ttl or DEFAULT_TTL
|
|
59
78
|
limite = _rate_limit(rate_limit)
|
|
60
79
|
|
|
61
|
-
@router.post(
|
|
62
|
-
|
|
80
|
+
@router.post(
|
|
81
|
+
"/request",
|
|
82
|
+
dependencies=limite,
|
|
83
|
+
response_model=MagicLinkRequested,
|
|
84
|
+
# `exclude_none`: antes de este `response_model` la ruta devolvía un `dict` que
|
|
85
|
+
# directamente omitía la clave `token` cuando era `None`. Sin esto, el modelo la
|
|
86
|
+
# incluiría como `null` en los dos casos — no filtra nada (la ruta sigue
|
|
87
|
+
# respondiendo igual exista o no la cuenta), pero cambiaría el shape que un
|
|
88
|
+
# cliente ya integrado contra el dict viejo espera.
|
|
89
|
+
response_model_exclude_none=True,
|
|
90
|
+
)
|
|
91
|
+
async def request(payload: RequestMagicLinkBody) -> MagicLinkRequested:
|
|
63
92
|
"""
|
|
64
93
|
Pide un magic link. **Responde igual exista o no la cuenta.**
|
|
65
94
|
|
|
@@ -74,12 +103,9 @@ def build_magic_link_router(
|
|
|
74
103
|
|
|
75
104
|
# La forma de la respuesta es idéntica en los dos casos. Que el token venga o no es lo
|
|
76
105
|
# único que cambia, y en producción no se devuelve.
|
|
77
|
-
|
|
78
|
-
if emitido.token is not None:
|
|
79
|
-
cuerpo["token"] = emitido.token
|
|
80
|
-
return cuerpo
|
|
106
|
+
return MagicLinkRequested(sent=True, token=emitido.token)
|
|
81
107
|
|
|
82
|
-
@router.post("/consume")
|
|
108
|
+
@router.post("/consume", response_model=SessionResponse)
|
|
83
109
|
async def consume(
|
|
84
110
|
payload: ConsumeMagicLinkBody,
|
|
85
111
|
request: Request,
|
|
@@ -25,9 +25,12 @@ from fastapi import APIRouter, Depends, Query, Request, Response
|
|
|
25
25
|
from fastapi.responses import JSONResponse
|
|
26
26
|
from pydantic import BaseModel
|
|
27
27
|
|
|
28
|
+
from hexcore.darwin.infrastructure.api.routers import SessionResponse
|
|
29
|
+
|
|
28
30
|
__all__ = [
|
|
29
31
|
"AuthorizationResponse",
|
|
30
32
|
"LinkedProviders",
|
|
33
|
+
"OAuthCallbackResponse",
|
|
31
34
|
"build_oauth_router",
|
|
32
35
|
]
|
|
33
36
|
|
|
@@ -50,6 +53,17 @@ class LinkedProviders(BaseModel):
|
|
|
50
53
|
providers: list[str]
|
|
51
54
|
|
|
52
55
|
|
|
56
|
+
class OAuthCallbackResponse(SessionResponse):
|
|
57
|
+
"""
|
|
58
|
+
La respuesta de `GET /auth/oauth/{provider}/callback`.
|
|
59
|
+
|
|
60
|
+
`created` distingue un signup implícito de un login: es lo que el cliente necesita para
|
|
61
|
+
mandar a un usuario nuevo a onboarding en vez de al home.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
created: bool
|
|
65
|
+
|
|
66
|
+
|
|
53
67
|
def build_oauth_router(
|
|
54
68
|
*,
|
|
55
69
|
prefix: str = "/auth/oauth",
|
|
@@ -84,7 +98,12 @@ def build_oauth_router(
|
|
|
84
98
|
|
|
85
99
|
return LinkedProviders(providers=list(get_oauth_service().provider_ids))
|
|
86
100
|
|
|
87
|
-
@router.get(
|
|
101
|
+
@router.get(
|
|
102
|
+
"/{provider}/start",
|
|
103
|
+
dependencies=limite,
|
|
104
|
+
response_model=AuthorizationResponse,
|
|
105
|
+
responses={302: {"description": "Con `?redirect=1`: redirect directo al proveedor."}},
|
|
106
|
+
)
|
|
88
107
|
async def start(
|
|
89
108
|
provider: str,
|
|
90
109
|
redirect_uri: str = Query(..., description="A dónde vuelve el usuario."),
|
|
@@ -110,7 +129,9 @@ def build_oauth_router(
|
|
|
110
129
|
).model_dump()
|
|
111
130
|
)
|
|
112
131
|
|
|
113
|
-
@router.get(
|
|
132
|
+
@router.get(
|
|
133
|
+
"/{provider}/callback", dependencies=limite, response_model=OAuthCallbackResponse
|
|
134
|
+
)
|
|
114
135
|
async def callback(
|
|
115
136
|
provider: str,
|
|
116
137
|
request: Request,
|
|
@@ -30,6 +30,7 @@ __all__ = [
|
|
|
30
30
|
"OrganizationOut",
|
|
31
31
|
"MemberOut",
|
|
32
32
|
"InvitationOut",
|
|
33
|
+
"InvitationIssued",
|
|
33
34
|
"build_organization_router",
|
|
34
35
|
]
|
|
35
36
|
|
|
@@ -87,11 +88,19 @@ class InvitationOut(BaseModel):
|
|
|
87
88
|
"""
|
|
88
89
|
|
|
89
90
|
id: str
|
|
91
|
+
|
|
90
92
|
email: str
|
|
91
93
|
role: OrgRole
|
|
92
94
|
expires_at: str
|
|
93
95
|
|
|
94
96
|
|
|
97
|
+
class InvitationIssued(BaseModel):
|
|
98
|
+
"""La respuesta de `POST /organizations/{organization_id}/invitations`."""
|
|
99
|
+
|
|
100
|
+
invitation: InvitationOut
|
|
101
|
+
token: str
|
|
102
|
+
|
|
103
|
+
|
|
95
104
|
def build_organization_router(
|
|
96
105
|
*,
|
|
97
106
|
prefix: str = "/organizations",
|
|
@@ -240,12 +249,16 @@ def build_organization_router(
|
|
|
240
249
|
return {"removed": True}
|
|
241
250
|
|
|
242
251
|
# ── Las invitaciones ──────────────────────────────────────────────────────
|
|
243
|
-
@router.post(
|
|
252
|
+
@router.post(
|
|
253
|
+
"/{organization_id}/invitations",
|
|
254
|
+
status_code=201,
|
|
255
|
+
response_model=InvitationIssued,
|
|
256
|
+
)
|
|
244
257
|
async def invitar(
|
|
245
258
|
organization_id: UUID,
|
|
246
259
|
payload: InviteBody,
|
|
247
260
|
auth: t.Any = Depends(provide_auth),
|
|
248
|
-
) ->
|
|
261
|
+
) -> InvitationIssued:
|
|
249
262
|
"""
|
|
250
263
|
Invita a alguien. Requiere ser `admin` o más, y no se puede invitar por encima del rol
|
|
251
264
|
propio.
|
|
@@ -262,10 +275,9 @@ def build_organization_router(
|
|
|
262
275
|
email=payload.email,
|
|
263
276
|
role=payload.role,
|
|
264
277
|
)
|
|
265
|
-
return
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
278
|
+
return InvitationIssued(
|
|
279
|
+
invitation=_invitation_out(emitida.invitation), token=emitida.token
|
|
280
|
+
)
|
|
269
281
|
|
|
270
282
|
@router.get("/{organization_id}/invitations")
|
|
271
283
|
async def pendientes(
|
|
@@ -23,6 +23,8 @@ from fastapi import APIRouter, Depends, Request, Response
|
|
|
23
23
|
from fastapi.responses import JSONResponse
|
|
24
24
|
from pydantic import BaseModel, Field
|
|
25
25
|
|
|
26
|
+
from hexcore.darwin.infrastructure.api.routers import SessionResponse
|
|
27
|
+
|
|
26
28
|
__all__ = [
|
|
27
29
|
"FinishRegistrationBody",
|
|
28
30
|
"AuthenticationOptionsBody",
|
|
@@ -99,7 +101,20 @@ def build_passkey_router(
|
|
|
99
101
|
limite = _rate_limit(rate_limit)
|
|
100
102
|
|
|
101
103
|
# ── Registro (con sesión) ─────────────────────────────────────────────────
|
|
102
|
-
|
|
104
|
+
# Sin `response_model`: la forma es la de `PublicKeyCredentialCreationOptions` del spec
|
|
105
|
+
# WebAuthn (https://www.w3.org/TR/webauthn-3/#dictionary-makecredentialoptions).
|
|
106
|
+
# Remodelarla en pydantic sería mantener una copia de un estándar ajeno para ganar un
|
|
107
|
+
# schema que un cliente ya escribe a mano contra el spec — `openapi_extra` documenta la
|
|
108
|
+
# referencia sin pagar esa copia.
|
|
109
|
+
@router.post(
|
|
110
|
+
"/register/options",
|
|
111
|
+
openapi_extra={
|
|
112
|
+
"externalDocs": {
|
|
113
|
+
"description": "PublicKeyCredentialCreationOptions (WebAuthn L3)",
|
|
114
|
+
"url": "https://www.w3.org/TR/webauthn-3/#dictionary-makecredentialoptions",
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
)
|
|
103
118
|
async def register_options(
|
|
104
119
|
auth: t.Any = Depends(provide_auth),
|
|
105
120
|
) -> dict[str, t.Any]:
|
|
@@ -140,7 +155,18 @@ def build_passkey_router(
|
|
|
140
155
|
return _resumen(guardada)
|
|
141
156
|
|
|
142
157
|
# ── Login (público) ───────────────────────────────────────────────────────
|
|
143
|
-
|
|
158
|
+
# Mismo motivo que `/register/options`: la forma es la de
|
|
159
|
+
# `PublicKeyCredentialRequestOptions` del spec WebAuthn.
|
|
160
|
+
@router.post(
|
|
161
|
+
"/authenticate/options",
|
|
162
|
+
dependencies=limite,
|
|
163
|
+
openapi_extra={
|
|
164
|
+
"externalDocs": {
|
|
165
|
+
"description": "PublicKeyCredentialRequestOptions (WebAuthn L3)",
|
|
166
|
+
"url": "https://www.w3.org/TR/webauthn-3/#dictionary-assertion-options",
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
)
|
|
144
170
|
async def authenticate_options(
|
|
145
171
|
payload: AuthenticationOptionsBody,
|
|
146
172
|
) -> dict[str, t.Any]:
|
|
@@ -166,7 +192,9 @@ def build_passkey_router(
|
|
|
166
192
|
opciones = await get_passkey_service().start_authentication(user_id=user_id)
|
|
167
193
|
return opciones.options
|
|
168
194
|
|
|
169
|
-
@router.post(
|
|
195
|
+
@router.post(
|
|
196
|
+
"/authenticate", dependencies=limite, response_model=SessionResponse
|
|
197
|
+
)
|
|
170
198
|
async def authenticate(
|
|
171
199
|
payload: FinishAuthenticationBody,
|
|
172
200
|
request: Request,
|
|
@@ -179,6 +179,18 @@ class TwoFactorPlugin(DarwinPlugin):
|
|
|
179
179
|
def exception_status_map(self) -> t.Mapping[type[Exception], int]:
|
|
180
180
|
return TWO_FACTOR_EXCEPTION_STATUS_MAP
|
|
181
181
|
|
|
182
|
+
def exception_payload(self, exc: Exception) -> t.Mapping[str, t.Any]:
|
|
183
|
+
"""
|
|
184
|
+
El `challenge` de `TwoFactorRequiredError`, en el cuerpo del 401.
|
|
185
|
+
|
|
186
|
+
Sin esto el segundo paso del login no es completable desde HTTP: `POST
|
|
187
|
+
/auth/2fa/challenge` exige el `challenge` de vuelta, y hasta la Fase 0 el cliente
|
|
188
|
+
nunca lo recibía — sólo vivía en el atributo Python de la excepción.
|
|
189
|
+
"""
|
|
190
|
+
if isinstance(exc, TwoFactorRequiredError) and exc.challenge is not None:
|
|
191
|
+
return {"challenge": exc.challenge}
|
|
192
|
+
return {}
|
|
193
|
+
|
|
182
194
|
def hooks(self) -> t.Sequence[HookBinding]:
|
|
183
195
|
from hexcore.darwin.application.services import SIGN_IN_AUTHENTICATED
|
|
184
196
|
|
|
@@ -22,6 +22,8 @@ from fastapi import APIRouter, Depends, Request, Response
|
|
|
22
22
|
from fastapi.responses import JSONResponse
|
|
23
23
|
from pydantic import BaseModel
|
|
24
24
|
|
|
25
|
+
from hexcore.darwin.infrastructure.api.routers import SessionResponse
|
|
26
|
+
|
|
25
27
|
__all__ = [
|
|
26
28
|
"ConfirmBody",
|
|
27
29
|
"DisableBody",
|
|
@@ -156,7 +158,7 @@ def build_two_factor_router(
|
|
|
156
158
|
)
|
|
157
159
|
return {"disabled": True}
|
|
158
160
|
|
|
159
|
-
@router.post("/challenge", dependencies=limite)
|
|
161
|
+
@router.post("/challenge", dependencies=limite, response_model=SessionResponse)
|
|
160
162
|
async def challenge(
|
|
161
163
|
payload: ChallengeBody,
|
|
162
164
|
request: Request,
|