tempest-fastapi-sdk 0.48.0__tar.gz → 0.49.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 (391) hide show
  1. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/CHANGELOG.md +19 -0
  2. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/PKG-INFO +11 -1
  3. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/README.md +10 -0
  4. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/auth-flow.en.md +189 -21
  5. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/auth-flow.md +189 -21
  6. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/http.en.md +3 -0
  7. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/http.md +3 -0
  8. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/pyproject.toml +1 -1
  9. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/__init__.py +1 -1
  10. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/service.py +111 -3
  11. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/auth/test_service.py +148 -1
  12. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/uv.lock +1 -1
  13. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/.github/workflows/ci.yml +0 -0
  14. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/.github/workflows/docs.yml +0 -0
  15. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/.github/workflows/release-pypi.yml +0 -0
  16. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/.gitignore +0 -0
  17. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/.python-version +0 -0
  18. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/CLAUDE.md +0 -0
  19. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/Makefile +0 -0
  20. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/architecture.en.md +0 -0
  21. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/architecture.md +0 -0
  22. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/changelog.en.md +0 -0
  23. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/changelog.md +0 -0
  24. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/contributing.en.md +0 -0
  25. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/contributing.md +0 -0
  26. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/index.en.md +0 -0
  27. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/index.md +0 -0
  28. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/installation.en.md +0 -0
  29. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/installation.md +0 -0
  30. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/index.en.md +0 -0
  31. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/index.md +0 -0
  32. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/api.en.md +0 -0
  33. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/api.md +0 -0
  34. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/business-rules.en.md +0 -0
  35. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/business-rules.md +0 -0
  36. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/domain.en.md +0 -0
  37. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/domain.md +0 -0
  38. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/flows.en.md +0 -0
  39. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/flows.md +0 -0
  40. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/index.en.md +0 -0
  41. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/learning/marketplace/index.md +0 -0
  42. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/migration.en.md +0 -0
  43. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/migration.md +0 -0
  44. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/admin.en.md +0 -0
  45. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/admin.md +0 -0
  46. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/br-helpers.en.md +0 -0
  47. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/br-helpers.md +0 -0
  48. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/cache.en.md +0 -0
  49. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/cache.md +0 -0
  50. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/cli.en.md +0 -0
  51. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/cli.md +0 -0
  52. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/database.en.md +0 -0
  53. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/database.md +0 -0
  54. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/deploy-safety.en.md +0 -0
  55. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/deploy-safety.md +0 -0
  56. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/downloads.en.md +0 -0
  57. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/downloads.md +0 -0
  58. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/email.en.md +0 -0
  59. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/email.md +0 -0
  60. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/http-client.en.md +0 -0
  61. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/http-client.md +0 -0
  62. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/idempotency.en.md +0 -0
  63. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/idempotency.md +0 -0
  64. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/index.en.md +0 -0
  65. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/index.md +0 -0
  66. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/logging.en.md +0 -0
  67. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/logging.md +0 -0
  68. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/metrics.en.md +0 -0
  69. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/metrics.md +0 -0
  70. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/mfa.en.md +0 -0
  71. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/mfa.md +0 -0
  72. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/multi-tenant.en.md +0 -0
  73. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/multi-tenant.md +0 -0
  74. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/observability.en.md +0 -0
  75. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/observability.md +0 -0
  76. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/outbox.en.md +0 -0
  77. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/outbox.md +0 -0
  78. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/queue-tasks.en.md +0 -0
  79. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/queue-tasks.md +0 -0
  80. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/realtime.en.md +0 -0
  81. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/realtime.md +0 -0
  82. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/security.en.md +0 -0
  83. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/security.md +0 -0
  84. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/sessions.en.md +0 -0
  85. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/sessions.md +0 -0
  86. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/storage.en.md +0 -0
  87. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/storage.md +0 -0
  88. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/testing.en.md +0 -0
  89. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/testing.md +0 -0
  90. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/uploads.en.md +0 -0
  91. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/uploads.md +0 -0
  92. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/utilities.en.md +0 -0
  93. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/utilities.md +0 -0
  94. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/webpush.en.md +0 -0
  95. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/webpush.md +0 -0
  96. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/websocket.en.md +0 -0
  97. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/recipes/websocket.md +0 -0
  98. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/reference.en.md +0 -0
  99. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/reference.md +0 -0
  100. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/roadmap.en.md +0 -0
  101. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/roadmap.md +0 -0
  102. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/tutorial.en.md +0 -0
  103. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/docs/tutorial.md +0 -0
  104. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/mkdocs.yml +0 -0
  105. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/scripts/extract_recipe.py +0 -0
  106. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
  107. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
  108. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/config.py +0 -0
  109. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
  110. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/router.py +0 -0
  111. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/session.py +0 -0
  112. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/site.py +0 -0
  113. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
  114. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
  115. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
  116. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
  117. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
  118. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
  119. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
  120. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
  121. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
  122. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/__init__.py +0 -0
  123. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
  124. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
  125. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
  126. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
  127. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +0 -0
  128. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
  129. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
  130. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
  131. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
  132. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
  133. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
  134. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
  135. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
  136. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
  137. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
  138. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
  139. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
  140. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
  141. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/server.py +0 -0
  142. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/static.py +0 -0
  143. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
  144. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
  145. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
  146. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
  147. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/router.py +0 -0
  148. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
  149. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/activation.html +0 -0
  150. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/activation_error.html +0 -0
  151. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/activation_success.html +0 -0
  152. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/password_reset.html +0 -0
  153. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/password_reset_error.html +0 -0
  154. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/password_reset_form.html +0 -0
  155. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/auth/templates/password_reset_success.html +0 -0
  156. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
  157. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
  158. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
  159. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
  160. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
  161. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
  162. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
  163. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
  164. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
  165. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
  166. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
  167. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
  168. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
  169. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
  170. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
  171. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
  172. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
  173. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
  174. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
  175. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
  176. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
  177. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
  178. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
  179. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
  180. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
  181. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
  182. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
  183. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
  184. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
  185. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
  186. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/db.py +0 -0
  187. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
  188. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
  189. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
  190. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/main.py +0 -0
  191. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/new.py +0 -0
  192. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
  193. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
  194. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/cli/user.py +0 -0
  195. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
  196. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
  197. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
  198. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/core/context.py +0 -0
  199. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/core/enums.py +0 -0
  200. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/core/logging.py +0 -0
  201. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
  202. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
  203. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
  204. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
  205. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/connection.py +0 -0
  206. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
  207. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
  208. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/model.py +0 -0
  209. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
  210. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/repository.py +0 -0
  211. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
  212. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
  213. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
  214. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
  215. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
  216. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
  217. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
  218. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
  219. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
  220. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
  221. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
  222. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
  223. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
  224. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
  225. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
  226. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/py.typed +0 -0
  227. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
  228. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
  229. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
  230. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
  231. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
  232. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
  233. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
  234. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
  235. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
  236. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/services/base.py +0 -0
  237. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
  238. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
  239. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
  240. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
  241. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
  242. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
  243. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
  244. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
  245. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/settings/base.py +0 -0
  246. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
  247. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
  248. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
  249. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
  250. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
  251. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
  252. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
  253. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
  254. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
  255. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/testing/database.py +0 -0
  256. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
  257. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
  258. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
  259. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
  260. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/download.py +0 -0
  261. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/email.py +0 -0
  262. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
  263. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
  264. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/log.py +0 -0
  265. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
  266. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
  267. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/password.py +0 -0
  268. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
  269. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
  270. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
  271. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
  272. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
  273. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
  274. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
  275. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
  276. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
  277. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
  278. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
  279. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
  280. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/__init__.py +0 -0
  281. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/__init__.py +0 -0
  282. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_auth.py +0 -0
  283. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_forms.py +0 -0
  284. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_logs_nav.py +0 -0
  285. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_mfa.py +0 -0
  286. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_router.py +0 -0
  287. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_site.py +0 -0
  288. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/admin/test_user_model.py +0 -0
  289. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/__init__.py +0 -0
  290. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_body_size.py +0 -0
  291. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_cookies.py +0 -0
  292. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_cors.py +0 -0
  293. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_csrf.py +0 -0
  294. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_dependencies_auth.py +0 -0
  295. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_graceful.py +0 -0
  296. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_handlers.py +0 -0
  297. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_health_router.py +0 -0
  298. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_idempotency.py +0 -0
  299. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_jwt_dependency.py +0 -0
  300. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_logs_router.py +0 -0
  301. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_oauth.py +0 -0
  302. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_prometheus.py +0 -0
  303. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_rate_limit.py +0 -0
  304. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_request_id_middleware.py +0 -0
  305. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_role_dependency.py +0 -0
  306. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_server.py +0 -0
  307. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_static.py +0 -0
  308. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_tool_spec.py +0 -0
  309. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_tracing.py +0 -0
  310. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_webhooks.py +0 -0
  311. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/api/test_webhooks_rsa.py +0 -0
  312. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/auth/__init__.py +0 -0
  313. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/auth/test_mfa.py +0 -0
  314. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cache/__init__.py +0 -0
  315. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cache/test_decorator.py +0 -0
  316. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cache/test_redis_manager.py +0 -0
  317. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/__init__.py +0 -0
  318. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_db.py +0 -0
  319. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_db_seed.py +0 -0
  320. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_docker_compose.py +0 -0
  321. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_generate.py +0 -0
  322. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_main.py +0 -0
  323. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_secrets.py +0 -0
  324. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/cli/test_user.py +0 -0
  325. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/conftest.py +0 -0
  326. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/controllers/__init__.py +0 -0
  327. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/controllers/test_base.py +0 -0
  328. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/core/__init__.py +0 -0
  329. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/core/test_context.py +0 -0
  330. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/core/test_enums.py +0 -0
  331. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/core/test_logging.py +0 -0
  332. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/__init__.py +0 -0
  333. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_alembic_hooks.py +0 -0
  334. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_bulk_ops.py +0 -0
  335. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_connection.py +0 -0
  336. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_migrations.py +0 -0
  337. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_mixins.py +0 -0
  338. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_model.py +0 -0
  339. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_outbox.py +0 -0
  340. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_repository.py +0 -0
  341. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_safe_upgrade.py +0 -0
  342. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_slow_query.py +0 -0
  343. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/db/test_tenant.py +0 -0
  344. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/exceptions/__init__.py +0 -0
  345. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/exceptions/test_exceptions.py +0 -0
  346. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/queue/__init__.py +0 -0
  347. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/queue/test_manager.py +0 -0
  348. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/schemas/__init__.py +0 -0
  349. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/schemas/test_base.py +0 -0
  350. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/schemas/test_cursor_pagination.py +0 -0
  351. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/schemas/test_link_headers.py +0 -0
  352. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/schemas/test_pagination.py +0 -0
  353. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/schemas/test_response.py +0 -0
  354. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/services/__init__.py +0 -0
  355. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/services/test_base.py +0 -0
  356. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/sessions/__init__.py +0 -0
  357. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/sessions/test_sessions.py +0 -0
  358. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/settings/__init__.py +0 -0
  359. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/settings/test_base.py +0 -0
  360. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/settings/test_mixins.py +0 -0
  361. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/sse/__init__.py +0 -0
  362. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/sse/test_event_stream.py +0 -0
  363. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/storage/__init__.py +0 -0
  364. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/storage/test_minio_client.py +0 -0
  365. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/tasks/__init__.py +0 -0
  366. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/tasks/test_manager.py +0 -0
  367. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/tasks/test_scheduler.py +0 -0
  368. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/testing/__init__.py +0 -0
  369. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/testing/test_database.py +0 -0
  370. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/__init__.py +0 -0
  371. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_client_ip.py +0 -0
  372. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_datetime.py +0 -0
  373. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_dict.py +0 -0
  374. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_download.py +0 -0
  375. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_email.py +0 -0
  376. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_http_client.py +0 -0
  377. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_jwt.py +0 -0
  378. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_lazy_extras.py +0 -0
  379. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_log.py +0 -0
  380. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_metrics.py +0 -0
  381. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_opaque_token.py +0 -0
  382. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_password.py +0 -0
  383. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_regex.py +0 -0
  384. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_storage_backends.py +0 -0
  385. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_throttle.py +0 -0
  386. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/utils/test_upload.py +0 -0
  387. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/webpush/__init__.py +0 -0
  388. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/webpush/test_dispatcher.py +0 -0
  389. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/webpush/test_schemas.py +0 -0
  390. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/websockets/__init__.py +0 -0
  391. {tempest_fastapi_sdk-0.48.0 → tempest_fastapi_sdk-0.49.0}/tests/websockets/test_hub_and_router.py +0 -0
@@ -5,6 +5,25 @@ All notable changes to **tempest-fastapi-sdk** are listed below.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.49.0] — 2026-06-12
9
+
10
+ ### Added
11
+
12
+ - **`UserAuthService.current_user_dependency()` — built-in
13
+ authenticated-user dependency.** Projects no longer hand-write a
14
+ `load_user` callable plus a second `JWTUtils` to read the
15
+ `current_user` from a bearer token. The service now exposes
16
+ `get_user(subject, session)` (session-explicit), `load_user(subject)`
17
+ (opens its own session from the `db=` handle), and
18
+ `current_user_dependency(*, soft=False)` which wraps
19
+ `make_jwt_user_dependency` with the service's **own** `JWTUtils` and
20
+ `load_user` — so the token is verified with the same secret it was
21
+ signed with, eliminating the divergent-secret footgun. Wiring
22
+ collapses to `get_current_user = auth_service.current_user_dependency()`
23
+ / `auth_service.current_user_dependency(soft=True)`. Requires the
24
+ service to be built with `db=` (already an accepted constructor arg).
25
+ The auth-flow and HTTP recipes now teach this path.
26
+
8
27
  ## [0.48.0] — 2026-06-11
9
28
 
10
29
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tempest-fastapi-sdk
3
- Version: 0.48.0
3
+ Version: 0.49.0
4
4
  Summary: Shared FastAPI building blocks: base schemas, ORM model, async repository, exceptions, pagination and settings — the conventions used across Tempest projects.
5
5
  Project-URL: Homepage, https://github.com/mauriciobenjamin700/tempest-fastapi-sdk
6
6
  Project-URL: Repository, https://github.com/mauriciobenjamin700/tempest-fastapi-sdk
@@ -1156,6 +1156,16 @@ async def me(current: UserModel = Depends(get_current_user)) -> UserResponseSche
1156
1156
  return UserResponseSchema.model_validate(current)
1157
1157
  ```
1158
1158
 
1159
+ > **Using the bundled flow?** If you wire auth with `UserAuthService`
1160
+ > (built with `db=`), skip the hand-written `load_user` and the extra
1161
+ > `JWTUtils` — the service builds the dependency for you and verifies
1162
+ > the token with the same secret it signs with:
1163
+ >
1164
+ > ```python
1165
+ > get_current_user = auth_service.current_user_dependency()
1166
+ > get_current_user_or_none = auth_service.current_user_dependency(soft=True)
1167
+ > ```
1168
+
1159
1169
  #### Soft auth (optional user)
1160
1170
 
1161
1171
  `get_current_user_or_none` above already uses `soft=True` — it returns `None` instead of raising on a missing or invalid token, so endpoints can work both authenticated and anonymous:
@@ -1054,6 +1054,16 @@ async def me(current: UserModel = Depends(get_current_user)) -> UserResponseSche
1054
1054
  return UserResponseSchema.model_validate(current)
1055
1055
  ```
1056
1056
 
1057
+ > **Using the bundled flow?** If you wire auth with `UserAuthService`
1058
+ > (built with `db=`), skip the hand-written `load_user` and the extra
1059
+ > `JWTUtils` — the service builds the dependency for you and verifies
1060
+ > the token with the same secret it signs with:
1061
+ >
1062
+ > ```python
1063
+ > get_current_user = auth_service.current_user_dependency()
1064
+ > get_current_user_or_none = auth_service.current_user_dependency(soft=True)
1065
+ > ```
1066
+
1057
1067
  #### Soft auth (optional user)
1058
1068
 
1059
1069
  `get_current_user_or_none` above already uses `soft=True` — it returns `None` instead of raising on a missing or invalid token, so endpoints can work both authenticated and anonymous:
@@ -7,7 +7,7 @@ Since v0.31.0 the SDK ships the full local-account lifecycle — email + passwor
7
7
  1. **[Minimum setup](#minimum-setup)** — extras install + wiring four objects (`AsyncDatabaseManager`, `EmailUtils`, `UserAuthService`, `make_auth_router`).
8
8
  2. **[Concrete UserTokenModel](#concrete-usertokenmodel)** — `BaseUserTokenModel` is abstract, your project owns the concrete table.
9
9
  3. **[Endpoints](#endpoints)** — table of the 5 endpoints + payload + behavior.
10
- 4. **[Settings (`AuthSettings`)](#settings-authsettings)** — `.env` flag by flag.
10
+ 4. **[Settings — environment variables](#settings-environment-variables)** — env vars in **six groups** (JWT, password policy, email flow, TTL, URLs/templates, backend pages) — each in a typed table, not one blob.
11
11
  5. **[Email anatomy: how link, template and URL fit together](#email-anatomy)** — disambiguates the three concepts that confuse readers the most.
12
12
  6. **[Five operating modes](#five-operating-modes)** — production, dev with local SMTP (Mailhog / smtp4dev), dev without SMTP, CI without activation, and **backend-only** (links and pages served directly by the backend).
13
13
  7. **[Mailhog vs smtp4dev — which to pick for local dev](#mailhog-vs-smtp4dev)** — comparison + copy-paste docker-compose snippets.
@@ -53,6 +53,7 @@ emails = EmailUtils(
53
53
  )
54
54
 
55
55
  auth_service = UserAuthService(
56
+ db=db, # required for current_user_dependency (see final section)
56
57
  user_model=UserModel,
57
58
  token_model=UserTokenModel,
58
59
  auth_settings=settings, # mixes AuthSettings (see section 4)
@@ -131,9 +132,9 @@ uv run tempest db upgrade
131
132
 
132
133
  ---
133
134
 
134
- ## Settings (`AuthSettings`)
135
+ ## Settings — environment variables
135
136
 
136
- Mix `AuthSettings` into your `Settings` class:
137
+ Every knob in the flow comes from settings mixins. Mix them into your `Settings` class:
137
138
 
138
139
  ```python
139
140
  # src/core/settings.py
@@ -158,30 +159,99 @@ class Settings(
158
159
  pass
159
160
 
160
161
 
161
- settings = Settings()
162
+ settings: Settings = Settings()
162
163
  ```
163
164
 
164
- Variables (with production-safe defaults):
165
+ !!! info "The attribute name **is** the env var name"
166
+ Every attribute in the tables below is read from an environment variable of the **same name**, case-sensitive, **no prefix**. `AUTH_PASSWORD_MIN_LENGTH` in `.env` → `settings.AUTH_PASSWORD_MIN_LENGTH`. They all have defaults — you only set what you want to change.
165
167
 
166
- ```bash
167
- # .env — email flow
168
- AUTH_AUTO_ACTIVATE=false # true = skip activation, return JWT immediately
169
- AUTH_RETURN_TOKEN_IN_RESPONSE=false # true = link in body instead of email
170
- AUTH_PASSWORD_MIN_LENGTH=12 # fully configurable (1+, e.g. 4); single source of truth
171
- AUTH_PASSWORD_REQUIRE_COMPLEXITY=false # true = require lowercase + uppercase + digit + special AND force a floor of 8
168
+ The variables split across **two mixins** and **six concern groups**. They're separated on purpose: a password is not the same thing as an email, and authentication (JWT) is not the same thing as account activation.
172
169
 
173
- # .envtoken TTL
174
- AUTH_ACTIVATION_TTL_SECONDS=604800 # 7 days
175
- AUTH_PASSWORD_RESET_TTL_SECONDS=3600 # 1 hour
170
+ ### Group 1 Authentication / JWT (`JWTSettings`)
176
171
 
177
- # .env URLs pointing to YOUR FRONTEND (NOT the backend)
178
- AUTH_ACTIVATION_URL_TEMPLATE=https://app.example.com/activate?token={token}
179
- AUTH_PASSWORD_RESET_URL_TEMPLATE=https://app.example.com/reset?token={token}
172
+ Controls the signing and lifetime of the tokens login returns. **It's the same `JWT_SECRET` the `get_current_user` dependency uses to verify** (see [Getting the `current_user`](#getting-the-current_user-from-the-request)).
180
173
 
181
- # .env Jinja2 filenames inside EmailUtils.template_dir
182
- AUTH_ACTIVATION_TEMPLATE=activation.html
183
- AUTH_PASSWORD_RESET_TEMPLATE=password_reset.html
184
- ```
174
+ | Env var | Type | Default | What it does |
175
+ |---------|------|---------|--------------|
176
+ | `JWT_SECRET` | `str` (≥32 bytes) | `change-me-…-32` | HMAC secret that signs the JWT. **Must be overridden in production.** |
177
+ | `JWT_ALGORITHM` | `str` | `HS256` | JOSE algorithm. `HS256`/`HS512` (symmetric secret) or `RS256` (key pair). |
178
+ | `JWT_ACCESS_TTL_SECONDS` | `int` (≥1) | `3600` | **Access token** lifetime (1 h). Short by design — renew via refresh. |
179
+ | `JWT_REFRESH_TTL_SECONDS` | `int` (≥1) | `604800` | **Refresh token** lifetime (7 days). |
180
+ | `JWT_ISSUER` | `str \| None` | `None` | `iss` claim. `None` omits the claim. |
181
+
182
+ !!! danger "The default `JWT_SECRET` leaks tokens"
183
+ The default `change-me-change-me-change-me-32` exists only to boot locally. In production, **anyone** with the default can forge a valid JWT. Generate a strong secret (`openssl rand -base64 48`) and inject it via a secret manager — never commit it.
184
+
185
+ ### Group 2 — Password policy (`AuthSettings`)
186
+
187
+ | Env var | Type | Default | What it does |
188
+ |---------|------|---------|--------------|
189
+ | `AUTH_PASSWORD_MIN_LENGTH` | `int` (≥1) | `12` | Minimum length accepted on signup **and** reset. |
190
+ | `AUTH_PASSWORD_REQUIRE_COMPLEXITY` | `bool` | `false` | `true` = require 1 lowercase + 1 uppercase + 1 digit + 1 special character. |
191
+
192
+ These two interact — **this is where it usually gets confusing**. The exact rule:
193
+
194
+ - **`complexity=false` (default):** only length matters. Any password with `≥ AUTH_PASSWORD_MIN_LENGTH` characters passes, with no composition requirement.
195
+ - **`complexity=true`:** on top of the 4 character classes, the **effective** length floor becomes `max(AUTH_PASSWORD_MIN_LENGTH, 8)`. That is, an `AUTH_PASSWORD_MIN_LENGTH` below 8 is **ignored** while complexity is on.
196
+
197
+ Decision table:
198
+
199
+ | `MIN_LENGTH` | `REQUIRE_COMPLEXITY` | Password accepted when |
200
+ |--------------|----------------------|------------------------|
201
+ | `12` | `false` | `≥ 12` chars, any composition |
202
+ | `4` | `false` | `≥ 4` chars, any composition (low floor, dev-only) |
203
+ | `4` | `true` | `≥ 8` chars (floor 8 **overrides** the 4) **+** the 4 classes |
204
+ | `16` | `true` | `≥ 16` chars **+** the 4 classes |
205
+
206
+ !!! warning "The floor is the single source of truth"
207
+ The request schemas (`SignupSchema`, `PasswordResetConfirmSchema`) impose **no** length bound of their own — they delegate to these two vars. Lowering `AUTH_PASSWORD_MIN_LENGTH` to `4` genuinely relaxes validation on the route too. There is no hidden second limit in the schema "protecting" you.
208
+
209
+ ### Group 3 — Email flow control (`AuthSettings`)
210
+
211
+ Decide **whether** and **how** the link reaches the user. They map directly to the [five operating modes](#five-operating-modes).
212
+
213
+ | Env var | Type | Default | What it does |
214
+ |---------|------|---------|--------------|
215
+ | `AUTH_AUTO_ACTIVATE` | `bool` | `false` | `true` = user is born active, skips activation, signup returns the JWT pair directly (Mode D). **Never in production.** |
216
+ | `AUTH_RETURN_TOKEN_IN_RESPONSE` | `bool` | `false` | `true` = activation/reset link goes in the JSON body instead of the email (Mode C). |
217
+
218
+ ### Group 4 — Account token TTL (`AuthSettings`)
219
+
220
+ Lifetime of the **single-use** tokens (activation / reset) — distinct from the Group 1 JWTs.
221
+
222
+ | Env var | Type | Default | What it does |
223
+ |---------|------|---------|--------------|
224
+ | `AUTH_ACTIVATION_TTL_SECONDS` | `int` (≥60) | `604800` | Activation token lifetime (7 days). |
225
+ | `AUTH_PASSWORD_RESET_TTL_SECONDS` | `int` (≥60) | `3600` | Reset token lifetime (1 h). Shorter is safer. |
226
+
227
+ ### Group 5 — Email URLs and templates (`AuthSettings`)
228
+
229
+ | Env var | Type | Default | What it does |
230
+ |---------|------|---------|--------------|
231
+ | `AUTH_ACTIVATION_URL_TEMPLATE` | `str` | `http://localhost:3000/activate?token={token}` | URL that goes in the email; `{token}` is substituted. **Points at the frontend** (except in Mode E). |
232
+ | `AUTH_PASSWORD_RESET_URL_TEMPLATE` | `str` | `http://localhost:3000/reset-password?token={token}` | Same, for reset. |
233
+ | `AUTH_ACTIVATION_TEMPLATE` | `str` | `activation.html` | Jinja2 filename of the activation **email HTML**, resolved against `EmailUtils.template_dir`. |
234
+ | `AUTH_PASSWORD_RESET_TEMPLATE` | `str` | `password_reset.html` | Same, for reset. |
235
+
236
+ !!! warning "URL template ≠ Jinja2 template"
237
+ `*_URL_TEMPLATE` is a `.format()` string with `{token}` — it's the **link**. `*_TEMPLATE` is the name of an `.html` file — it's the **email that wraps the link**. Confusing the two is the #1 mistake. Full detail in [Email anatomy](#email-anatomy).
238
+
239
+ ### Group 6 — Backend-rendered pages (Mode E, `AuthSettings`)
240
+
241
+ Only relevant when `AUTH_BACKEND_LINKS=true`. See [Mode E](#five-operating-modes) for the full flow.
242
+
243
+ | Env var | Type | Default | What it does |
244
+ |---------|------|---------|--------------|
245
+ | `AUTH_BACKEND_LINKS` | `bool` | `false` | `true` = mounts 3 extra HTML endpoints; the email link points at the **backend**, not the frontend. |
246
+ | `AUTH_LOGIN_URL` | `str \| None` | `None` | Login URL on the "go to login" button of success pages. `None` hides the button. |
247
+ | `AUTH_ACTIVATION_SUCCESS_TEMPLATE` | `str` | `activation_success.html` | Activation OK HTML page. |
248
+ | `AUTH_ACTIVATION_ERROR_TEMPLATE` | `str` | `activation_error.html` | Activation error HTML page. |
249
+ | `AUTH_PASSWORD_RESET_FORM_TEMPLATE` | `str` | `password_reset_form.html` | New-password HTML form. |
250
+ | `AUTH_PASSWORD_RESET_SUCCESS_TEMPLATE` | `str` | `password_reset_success.html` | Reset OK HTML page. |
251
+ | `AUTH_PASSWORD_RESET_ERROR_TEMPLATE` | `str` | `password_reset_error.html` | Reset error HTML page. |
252
+
253
+ !!! note "MFA / TOTP has its own vars"
254
+ When `AUTH_MFA_ENABLED=true`, `AuthSettings` also exposes `AUTH_MFA_ISSUER`, `AUTH_MFA_RECOVERY_CODES_COUNT`, `AUTH_MFA_TOKEN_TTL_SECONDS` and `AUTH_MFA_VERIFY_WINDOW`. They're out of scope for this recipe (signup/activate/login/reset) — covered in the MFA recipe.
185
255
 
186
256
  ---
187
257
 
@@ -515,6 +585,104 @@ emails/ # ← template_dir="emails"
515
585
 
516
586
  ---
517
587
 
588
+ ## Getting the `current_user` from the request
589
+
590
+ `make_auth_router` **issues** the JWT pair (login/activate return `access_token` + `refresh_token`). But what next? When the frontend sends `Authorization: Bearer <access_token>` to **your own** routes, you need a dependency that decodes the token and resolves the user.
591
+
592
+ Since v0.49.0, `UserAuthService` builds that dependency for you — `current_user_dependency()`. It:
593
+
594
+ 1. Reads `Authorization: Bearer <jwt>` via `HTTPBearer`.
595
+ 2. Decodes and verifies the JWT with **the same `JWTUtils` the service signs with** — no second secret to keep in sync.
596
+ 3. Pulls the `sub` (user id) from the payload, opens a session from `db=`, and returns the persisted `UserModel`.
597
+
598
+ ### 1. Declare the dependency once
599
+
600
+ The service already has `user_model`, `JWTUtils` and the session — so you don't write `load_user` by hand. Group both variants in `src/api/dependencies/auth.py`:
601
+
602
+ ```python
603
+ # src/api/dependencies/auth.py
604
+ from src.api.app import auth_service
605
+
606
+ get_current_user = auth_service.current_user_dependency()
607
+ get_current_user_or_none = auth_service.current_user_dependency(soft=True)
608
+ ```
609
+
610
+ !!! info "Requires `db=` on `UserAuthService`"
611
+ `current_user_dependency` resolves the user by opening its own session, so the service must have been created with `db=` (the `AsyncDatabaseManager` from [Minimum setup](#minimum-setup)). Because it reuses the internal `self.jwt`, the token is verified with the **same** secret that signed it — the divergent-`JWT_SECRET` footgun is gone.
612
+
613
+ ??? note "No `UserAuthService`? Build the dependency by hand"
614
+ If your service doesn't use the bundled flow, the `make_jwt_user_dependency` primitive accepts any `JWTUtils` + a one-argument async `user_loader`:
615
+
616
+ ```python
617
+ from uuid import UUID
618
+
619
+ from tempest_fastapi_sdk import JWTUtils, make_jwt_user_dependency
620
+
621
+ from src.api.app import db
622
+ from src.core.settings import settings
623
+ from src.db.models import UserModel
624
+ from src.db.repositories import UserRepository
625
+
626
+ tokens: JWTUtils = JWTUtils(
627
+ secret=settings.JWT_SECRET,
628
+ algorithm=settings.JWT_ALGORITHM,
629
+ )
630
+
631
+
632
+ async def load_user(subject: str) -> UserModel:
633
+ """Resolve the JWT subject (a UUID string) to the persisted user."""
634
+ async with db.get_session_context() as session:
635
+ repo: UserRepository = UserRepository(session)
636
+ return await repo.get_by_id(UUID(subject))
637
+
638
+
639
+ get_current_user = make_jwt_user_dependency(tokens, load_user)
640
+ get_current_user_or_none = make_jwt_user_dependency(tokens, load_user, soft=True)
641
+ ```
642
+
643
+ Heads up: here `tokens` **must** use the same `JWT_SECRET` / `JWT_ALGORITHM` as login, otherwise every valid token is rejected.
644
+
645
+ ### 2. Inject it on the route with `Depends`
646
+
647
+ ```python
648
+ # src/api/routers/users.py
649
+ from fastapi import APIRouter, Depends
650
+
651
+ from src.api.dependencies.auth import get_current_user
652
+ from src.db.models import UserModel
653
+ from src.schemas import UserResponseSchema
654
+
655
+ router: APIRouter = APIRouter(prefix="/users", tags=["users"])
656
+
657
+
658
+ @router.get("/me")
659
+ async def me(current: UserModel = Depends(get_current_user)) -> UserResponseSchema:
660
+ """Return the user who owns the request's bearer token."""
661
+ return UserResponseSchema.model_validate(current)
662
+ ```
663
+
664
+ `current` **is** the `UserModel` the service resolved — typed, persisted, ready to use. Missing or invalid token → `401 UnauthorizedException` before the route body runs.
665
+
666
+ ### 3. Optional auth — `soft=True`
667
+
668
+ For routes that work both authenticated **and** anonymous (e.g. a public feed that personalizes when logged in), use the `soft` variant — it returns `None` instead of raising:
669
+
670
+ ```python
671
+ @router.get("/feed")
672
+ async def feed(
673
+ current: UserModel | None = Depends(get_current_user_or_none),
674
+ ) -> list[PostResponseSchema]:
675
+ """Public feed; personalizes the ranking when a user is logged in."""
676
+ if current is None:
677
+ return await feed_service.public()
678
+ return await feed_service.personalized(current.id)
679
+ ```
680
+
681
+ !!! tip "Role and permission are the next step"
682
+ When the route needs a **role** (`admin`) or **permission** (`users:write`) and not just "logged in", swap for `make_role_dependency` / `make_permission_dependency`. See the [HTTP recipe »](http.en.md) — same `JWTUtils`, same `Depends` pattern.
683
+
684
+ ---
685
+
518
686
  ## Next steps
519
687
 
520
688
  - **[Idempotency »](idempotency.en.md)** — protect `POST /auth/signup` from retries that would duplicate the row.
@@ -7,7 +7,7 @@ Desde v0.31.0 o SDK fornece o ciclo completo de conta local — signup com email
7
7
  1. **[Setup mínimo](#setup-minimo)** — instalação dos extras + wiring de quatro objetos (`AsyncDatabaseManager`, `EmailUtils`, `UserAuthService`, `make_auth_router`).
8
8
  2. **[UserTokenModel concreto](#usertokenmodel-concreto)** — `BaseUserTokenModel` é abstrato, projeto cria a tabela final.
9
9
  3. **[Endpoints](#endpoints)** — tabela dos 5 endpoints + payload + comportamento.
10
- 4. **[Settings (`AuthSettings`)](#settings-authsettings)** — `.env` flag a flag.
10
+ 4. **[Settings — variáveis de ambiente](#settings-variaveis-de-ambiente)** — env vars em **seis grupos** (JWT, política de senha, fluxo de e-mail, TTL, URLs/templates, páginas backend) — cada uma em tabela tipada, não num blob.
11
11
  5. **[Anatomia de um e-mail: como link, template e URL se encaixam](#anatomia-de-um-e-mail)** — desambigua os três conceitos que mais confundem.
12
12
  6. **[Cinco modos de operação](#cinco-modos-de-operacao)** — produção, dev com SMTP local (Mailhog / smtp4dev), dev sem SMTP, CI sem ativação e **backend-only** (links e páginas servidas direto pelo backend).
13
13
  7. **[Mailhog vs smtp4dev — qual escolher pra dev local](#mailhog-vs-smtp4dev)** — comparativo + receitas docker-compose copy-paste.
@@ -53,6 +53,7 @@ emails = EmailUtils(
53
53
  )
54
54
 
55
55
  auth_service = UserAuthService(
56
+ db=db, # necessário pra current_user_dependency (ver seção final)
56
57
  user_model=UserModel,
57
58
  token_model=UserTokenModel,
58
59
  auth_settings=settings, # mistura AuthSettings (ver seção 4)
@@ -131,9 +132,9 @@ uv run tempest db upgrade
131
132
 
132
133
  ---
133
134
 
134
- ## Settings (`AuthSettings`)
135
+ ## Settings — variáveis de ambiente
135
136
 
136
- Mixe `AuthSettings` na sua classe `Settings`:
137
+ Toda a config do flow vem de mixins de settings. Mixe-as na sua classe `Settings`:
137
138
 
138
139
  ```python
139
140
  # src/core/settings.py
@@ -158,30 +159,99 @@ class Settings(
158
159
  pass
159
160
 
160
161
 
161
- settings = Settings()
162
+ settings: Settings = Settings()
162
163
  ```
163
164
 
164
- Variáveis (com defaults seguros pra produção):
165
+ !!! info "Nome do atributo **é** o nome da env var"
166
+ Cada atributo das tabelas abaixo é lido de uma variável de ambiente de **mesmo nome**, case-sensitive, **sem prefixo**. `AUTH_PASSWORD_MIN_LENGTH` no `.env` → `settings.AUTH_PASSWORD_MIN_LENGTH`. Todos têm default — você só seta o que quer mudar.
165
167
 
166
- ```bash
167
- # .env — fluxo de e-mail
168
- AUTH_AUTO_ACTIVATE=false # true = pula activation, devolve JWT direto
169
- AUTH_RETURN_TOKEN_IN_RESPONSE=false # true = link no body em vez de e-mail
170
- AUTH_PASSWORD_MIN_LENGTH=12 # totalmente configurável (1+, ex.: 4); fonte única da verdade
171
- AUTH_PASSWORD_REQUIRE_COMPLEXITY=false # true = exige minúscula + maiúscula + dígito + especial E força piso de 8
168
+ As variáveis se dividem em **dois mixins** e **seis grupos de concern**. Elas estão separadas de propósito: senha não é a mesma coisa que e-mail, e autenticação (JWT) não é a mesma coisa que ativação de conta.
172
169
 
173
- # .envTTL dos tokens
174
- AUTH_ACTIVATION_TTL_SECONDS=604800 # 7 dias
175
- AUTH_PASSWORD_RESET_TTL_SECONDS=3600 # 1 hora
170
+ ### Grupo 1 Autenticação / JWT (`JWTSettings`)
176
171
 
177
- # .env URLs apontando para SEU FRONTEND (NÃO o backend)
178
- AUTH_ACTIVATION_URL_TEMPLATE=https://app.example.com/activate?token={token}
179
- AUTH_PASSWORD_RESET_URL_TEMPLATE=https://app.example.com/reset?token={token}
172
+ Controla a assinatura e validade dos tokens que o login devolve. **É o mesmo `JWT_SECRET` que a dependency `get_current_user` usa pra validar** (veja [Pegando o `current_user`](#pegando-o-current_user-da-requisicao)).
180
173
 
181
- # .env nomes dos arquivos Jinja2 dentro do template_dir do EmailUtils
182
- AUTH_ACTIVATION_TEMPLATE=activation.html
183
- AUTH_PASSWORD_RESET_TEMPLATE=password_reset.html
184
- ```
174
+ | Env var | Tipo | Default | O que faz |
175
+ |---------|------|---------|-----------|
176
+ | `JWT_SECRET` | `str` (≥32 bytes) | `change-me-…-32` | Segredo HMAC que assina o JWT. **Obrigatório trocar em produção.** |
177
+ | `JWT_ALGORITHM` | `str` | `HS256` | Algoritmo JOSE. `HS256`/`HS512` (segredo simétrico) ou `RS256` (par de chaves). |
178
+ | `JWT_ACCESS_TTL_SECONDS` | `int` (≥1) | `3600` | Validade do **access token** (1 h). Curto por design — renove via refresh. |
179
+ | `JWT_REFRESH_TTL_SECONDS` | `int` (≥1) | `604800` | Validade do **refresh token** (7 dias). |
180
+ | `JWT_ISSUER` | `str \| None` | `None` | Claim `iss`. `None` omite o claim. |
181
+
182
+ !!! danger "`JWT_SECRET` default vaza tokens"
183
+ O default `change-me-change-me-change-me-32` existe só pra subir local. Em produção, **qualquer um** com o default consegue forjar um JWT válido. Gere um segredo forte (`openssl rand -base64 48`) e injete por secret manager — nunca commite.
184
+
185
+ ### Grupo 2 — Política de senha (`AuthSettings`)
186
+
187
+ | Env var | Tipo | Default | O que faz |
188
+ |---------|------|---------|-----------|
189
+ | `AUTH_PASSWORD_MIN_LENGTH` | `int` (≥1) | `12` | Comprimento mínimo aceito no signup **e** no reset. |
190
+ | `AUTH_PASSWORD_REQUIRE_COMPLEXITY` | `bool` | `false` | `true` = exige 1 minúscula + 1 maiúscula + 1 dígito + 1 caractere especial. |
191
+
192
+ Os dois interagem — **é aqui que costuma confundir**. A regra exata:
193
+
194
+ - **`complexity=false` (default):** só o comprimento importa. Qualquer senha com `≥ AUTH_PASSWORD_MIN_LENGTH` caracteres passa, sem exigência de composição.
195
+ - **`complexity=true`:** além das 4 classes de caracteres, o piso de comprimento **efetivo** vira `max(AUTH_PASSWORD_MIN_LENGTH, 8)`. Ou seja, um `AUTH_PASSWORD_MIN_LENGTH` abaixo de 8 é **ignorado** enquanto a complexidade está ligada.
196
+
197
+ Tabela de decisão:
198
+
199
+ | `MIN_LENGTH` | `REQUIRE_COMPLEXITY` | Senha aceita quando |
200
+ |--------------|---------------------|---------------------|
201
+ | `12` | `false` | `≥ 12` chars, qualquer composição |
202
+ | `4` | `false` | `≥ 4` chars, qualquer composição (piso baixo, dev-only) |
203
+ | `4` | `true` | `≥ 8` chars (piso 8 **sobrescreve** o 4) **+** as 4 classes |
204
+ | `16` | `true` | `≥ 16` chars **+** as 4 classes |
205
+
206
+ !!! warning "O piso é fonte única da verdade"
207
+ Os schemas de request (`SignupSchema`, `PasswordResetConfirmSchema`) **não** impõem limite próprio de comprimento — eles delegam pra essas duas vars. Baixar `AUTH_PASSWORD_MIN_LENGTH` pra `4` realmente afrouxa a validação na rota também. Não há um segundo limite escondido no schema "te protegendo".
208
+
209
+ ### Grupo 3 — Controle do fluxo de e-mail (`AuthSettings`)
210
+
211
+ Decidem **se** e **como** o link chega ao usuário. Mapeiam direto nos [cinco modos de operação](#cinco-modos-de-operacao).
212
+
213
+ | Env var | Tipo | Default | O que faz |
214
+ |---------|------|---------|-----------|
215
+ | `AUTH_AUTO_ACTIVATE` | `bool` | `false` | `true` = user nasce ativo, pula activation, signup devolve JWT pair direto (Modo D). **Nunca em produção.** |
216
+ | `AUTH_RETURN_TOKEN_IN_RESPONSE` | `bool` | `false` | `true` = link de ativação/reset vai no corpo JSON em vez do e-mail (Modo C). |
217
+
218
+ ### Grupo 4 — TTL dos tokens de conta (`AuthSettings`)
219
+
220
+ Validade dos tokens **de uso único** (ativação / reset) — distintos dos JWT do grupo 1.
221
+
222
+ | Env var | Tipo | Default | O que faz |
223
+ |---------|------|---------|-----------|
224
+ | `AUTH_ACTIVATION_TTL_SECONDS` | `int` (≥60) | `604800` | Validade do token de ativação (7 dias). |
225
+ | `AUTH_PASSWORD_RESET_TTL_SECONDS` | `int` (≥60) | `3600` | Validade do token de reset (1 h). Curto é mais seguro. |
226
+
227
+ ### Grupo 5 — URLs e templates de e-mail (`AuthSettings`)
228
+
229
+ | Env var | Tipo | Default | O que faz |
230
+ |---------|------|---------|-----------|
231
+ | `AUTH_ACTIVATION_URL_TEMPLATE` | `str` | `http://localhost:3000/activate?token={token}` | URL que vai no e-mail; `{token}` é substituído. **Aponta pro frontend** (exceto no Modo E). |
232
+ | `AUTH_PASSWORD_RESET_URL_TEMPLATE` | `str` | `http://localhost:3000/reset-password?token={token}` | Idem, para reset. |
233
+ | `AUTH_ACTIVATION_TEMPLATE` | `str` | `activation.html` | Nome do arquivo Jinja2 do **HTML do e-mail** de ativação, resolvido no `template_dir` do `EmailUtils`. |
234
+ | `AUTH_PASSWORD_RESET_TEMPLATE` | `str` | `password_reset.html` | Idem, para reset. |
235
+
236
+ !!! warning "URL template ≠ Jinja2 template"
237
+ `*_URL_TEMPLATE` é uma string `.format()` com `{token}` — é o **link**. `*_TEMPLATE` é o nome de um arquivo `.html` — é o **e-mail que embrulha o link**. Confundir os dois é o erro nº 1. Detalhe completo em [Anatomia de um e-mail](#anatomia-de-um-e-mail).
238
+
239
+ ### Grupo 6 — Páginas renderizadas pelo backend (Modo E, `AuthSettings`)
240
+
241
+ Só relevantes quando `AUTH_BACKEND_LINKS=true`. Veja o [Modo E](#cinco-modos-de-operacao) para o fluxo completo.
242
+
243
+ | Env var | Tipo | Default | O que faz |
244
+ |---------|------|---------|-----------|
245
+ | `AUTH_BACKEND_LINKS` | `bool` | `false` | `true` = monta 3 endpoints HTML extras; o link do e-mail aponta pro **backend**, não pro frontend. |
246
+ | `AUTH_LOGIN_URL` | `str \| None` | `None` | URL de login no botão "Ir pro login" das páginas de sucesso. `None` esconde o botão. |
247
+ | `AUTH_ACTIVATION_SUCCESS_TEMPLATE` | `str` | `activation_success.html` | Página HTML de ativação OK. |
248
+ | `AUTH_ACTIVATION_ERROR_TEMPLATE` | `str` | `activation_error.html` | Página HTML de ativação com erro. |
249
+ | `AUTH_PASSWORD_RESET_FORM_TEMPLATE` | `str` | `password_reset_form.html` | Form HTML de nova senha. |
250
+ | `AUTH_PASSWORD_RESET_SUCCESS_TEMPLATE` | `str` | `password_reset_success.html` | Página HTML de reset OK. |
251
+ | `AUTH_PASSWORD_RESET_ERROR_TEMPLATE` | `str` | `password_reset_error.html` | Página HTML de reset com erro. |
252
+
253
+ !!! note "MFA / TOTP tem suas próprias vars"
254
+ Quando `AUTH_MFA_ENABLED=true`, o `AuthSettings` ainda expõe `AUTH_MFA_ISSUER`, `AUTH_MFA_RECOVERY_CODES_COUNT`, `AUTH_MFA_TOKEN_TTL_SECONDS` e `AUTH_MFA_VERIFY_WINDOW`. Ficam fora do escopo desta receita (signup/activate/login/reset) — são cobertos na receita de MFA.
185
255
 
186
256
  ---
187
257
 
@@ -515,6 +585,104 @@ emails/ # ← template_dir="emails"
515
585
 
516
586
  ---
517
587
 
588
+ ## Pegando o `current_user` da requisição
589
+
590
+ `make_auth_router` **emite** o JWT pair (login/activate devolvem `access_token` + `refresh_token`). Mas e depois? Quando o frontend manda `Authorization: Bearer <access_token>` nas **suas próprias** rotas, você precisa de uma dependency que decodifica o token e resolve o usuário.
591
+
592
+ Desde a v0.49.0, o próprio `UserAuthService` constrói essa dependency — `current_user_dependency()`. Ela:
593
+
594
+ 1. Lê `Authorization: Bearer <jwt>` via `HTTPBearer`.
595
+ 2. Decodifica e verifica o JWT com **o mesmo `JWTUtils` que o service usou pra assinar** — sem segundo segredo pra manter sincronizado.
596
+ 3. Pega o `sub` (id do usuário) do payload, abre uma sessão a partir do `db=` e devolve o `UserModel` persistido.
597
+
598
+ ### 1. Declare a dependency uma vez
599
+
600
+ O service já tem `user_model`, `JWTUtils` e a sessão — então não precisa escrever `load_user` à mão. Junte as duas variantes no `src/api/dependencies/auth.py`:
601
+
602
+ ```python
603
+ # src/api/dependencies/auth.py
604
+ from src.api.app import auth_service
605
+
606
+ get_current_user = auth_service.current_user_dependency()
607
+ get_current_user_or_none = auth_service.current_user_dependency(soft=True)
608
+ ```
609
+
610
+ !!! info "Requer `db=` no `UserAuthService`"
611
+ `current_user_dependency` resolve o usuário abrindo a própria sessão, então o service precisa ter sido criado com `db=` (o `AsyncDatabaseManager` do [Setup mínimo](#setup-minimo)). Como reusa o `self.jwt` interno, o token é validado com o **mesmo** segredo que assinou — o footgun de `JWT_SECRET` divergente some.
612
+
613
+ ??? note "Sem `UserAuthService`? Monte a dependency na mão"
614
+ Se o seu serviço não usa o flow bundled, a primitiva `make_jwt_user_dependency` aceita qualquer `JWTUtils` + um `user_loader` async de um argumento:
615
+
616
+ ```python
617
+ from uuid import UUID
618
+
619
+ from tempest_fastapi_sdk import JWTUtils, make_jwt_user_dependency
620
+
621
+ from src.api.app import db
622
+ from src.core.settings import settings
623
+ from src.db.models import UserModel
624
+ from src.db.repositories import UserRepository
625
+
626
+ tokens: JWTUtils = JWTUtils(
627
+ secret=settings.JWT_SECRET,
628
+ algorithm=settings.JWT_ALGORITHM,
629
+ )
630
+
631
+
632
+ async def load_user(subject: str) -> UserModel:
633
+ """Resolve o subject do JWT (uma string UUID) para o usuário persistido."""
634
+ async with db.get_session_context() as session:
635
+ repo: UserRepository = UserRepository(session)
636
+ return await repo.get_by_id(UUID(subject))
637
+
638
+
639
+ get_current_user = make_jwt_user_dependency(tokens, load_user)
640
+ get_current_user_or_none = make_jwt_user_dependency(tokens, load_user, soft=True)
641
+ ```
642
+
643
+ Atenção: aqui o `tokens` **precisa** usar o mesmo `JWT_SECRET` / `JWT_ALGORITHM` do login, senão todo token válido é rejeitado.
644
+
645
+ ### 2. Injete na rota com `Depends`
646
+
647
+ ```python
648
+ # src/api/routers/users.py
649
+ from fastapi import APIRouter, Depends
650
+
651
+ from src.api.dependencies.auth import get_current_user
652
+ from src.db.models import UserModel
653
+ from src.schemas import UserResponseSchema
654
+
655
+ router: APIRouter = APIRouter(prefix="/users", tags=["users"])
656
+
657
+
658
+ @router.get("/me")
659
+ async def me(current: UserModel = Depends(get_current_user)) -> UserResponseSchema:
660
+ """Devolve o usuário dono do bearer token da requisição."""
661
+ return UserResponseSchema.model_validate(current)
662
+ ```
663
+
664
+ `current` **é** o `UserModel` que o service resolveu — tipado, persistido, pronto pra uso. Token ausente ou inválido → `401 UnauthorizedException` antes do corpo da rota rodar.
665
+
666
+ ### 3. Auth opcional — `soft=True`
667
+
668
+ Para rotas que funcionam logado **e** anônimo (ex.: feed público que personaliza se houver login), use a variante `soft` — ela devolve `None` em vez de levantar:
669
+
670
+ ```python
671
+ @router.get("/feed")
672
+ async def feed(
673
+ current: UserModel | None = Depends(get_current_user_or_none),
674
+ ) -> list[PostResponseSchema]:
675
+ """Feed público; personaliza o ranking quando há usuário logado."""
676
+ if current is None:
677
+ return await feed_service.public()
678
+ return await feed_service.personalized(current.id)
679
+ ```
680
+
681
+ !!! tip "Role e permission são o próximo passo"
682
+ Quando a rota precisa de **papel** (`admin`) ou **permissão** (`users:write`) e não só "estar logado", troque por `make_role_dependency` / `make_permission_dependency`. Veja a [receita HTTP »](http.md) — mesma `JWTUtils`, mesmo padrão de `Depends`.
683
+
684
+ ---
685
+
518
686
  ## Próximos passos
519
687
 
520
688
  - **[Idempotência »](idempotency.md)** — proteja `POST /auth/signup` de retentativas que duplicariam linha.
@@ -114,6 +114,9 @@ Four dependency factories live in `tempest_fastapi_sdk.api.dependencies.auth`
114
114
  | `make_jwt_user_dependency(tokens, user_loader, soft=False, subject_claim="sub")` | Decode the bearer JWT, await `user_loader(subject)`, return the loaded user. |
115
115
  | `make_role_dependency(tokens, ["admin"], require_all=False, roles_claim="roles")` / `make_permission_dependency(tokens, ["users:write"], require_all=True, permissions_claim="permissions")` | Decode the bearer JWT and gate the route on roles / permissions. |
116
116
 
117
+ !!! tip "Using the bundled flow? Skip `load_user`"
118
+ If you wire auth with `UserAuthService` + `make_auth_router`, you don't write `load_user` or instantiate a `JWTUtils` here — call `auth_service.current_user_dependency()` (and `.current_user_dependency(soft=True)`), which reuses the service's internal `JWTUtils`. See the [auth recipe »](auth-flow.en.md#getting-the-current_user-from-the-request). The example below is the manual wiring, for when you're **not** using the service.
119
+
117
120
  ```python
118
121
  # src/api/dependencies/auth.py
119
122
  from uuid import UUID
@@ -114,6 +114,9 @@ Quatro factories de dependência vivem em `tempest_fastapi_sdk.api.dependencies.
114
114
  | `make_jwt_user_dependency(tokens, user_loader, soft=False, subject_claim="sub")` | Decodifica o bearer JWT, aguarda `user_loader(subject)`, retorna o usuário carregado. |
115
115
  | `make_role_dependency(tokens, ["admin"], require_all=False, roles_claim="roles")` / `make_permission_dependency(tokens, ["users:write"], require_all=True, permissions_claim="permissions")` | Decodifica o bearer JWT e controla a rota por roles / permissões. |
116
116
 
117
+ !!! tip "Usa o flow bundled? Pule o `load_user`"
118
+ Se você monta auth com `UserAuthService` + `make_auth_router`, não precisa escrever `load_user` nem instanciar um `JWTUtils` aqui — chame `auth_service.current_user_dependency()` (e `.current_user_dependency(soft=True)`), que reusa o `JWTUtils` interno do service. Veja a [receita de auth »](auth-flow.md#pegando-o-current_user-da-requisicao). O exemplo abaixo é a montagem manual, pra quando você **não** usa o service.
119
+
117
120
  ```python
118
121
  # src/api/dependencies/auth.py
119
122
  from uuid import UUID
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tempest-fastapi-sdk"
3
- version = "0.48.0"
3
+ version = "0.49.0"
4
4
  description = "Shared FastAPI building blocks: base schemas, ORM model, async repository, exceptions, pagination and settings — the conventions used across Tempest projects."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -250,7 +250,7 @@ from tempest_fastapi_sdk.websockets import (
250
250
  make_websocket_router,
251
251
  )
252
252
 
253
- __version__: str = "0.48.0"
253
+ __version__: str = "0.49.0"
254
254
 
255
255
  __all__: list[str] = [
256
256
  "BASE_COLUMN_ORDER",