csrd-utils 0.3.93__tar.gz → 0.3.95__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 (203) hide show
  1. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/PKG-INFO +1 -1
  2. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/pyproject.toml +1 -1
  3. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/augments.py +9 -0
  4. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/renderer.py +12 -0
  5. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/scaffolder.py +14 -0
  6. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/service_renderers.py +20 -4
  7. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/auth-passthrough/delegates/auth_delegate.py +35 -0
  8. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/auth-passthrough/dependencies/auth_passthrough.py +21 -0
  9. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/auth-passthrough/models/auth_passthrough.py +32 -0
  10. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/auth-passthrough/views/auth_passthrough_view.py +25 -0
  11. csrd_utils-0.3.93/src/csrd_utils/v2/templates/augments/db-config/dependencies/db.py → csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/db-config/dependencies/db.py.template +10 -4
  12. csrd_utils-0.3.93/src/csrd_utils/v2/templates/augments/jwt-auth-consumer/dependencies/auth.py → csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-consumer/dependencies/auth.py.template +5 -0
  13. csrd_utils-0.3.93/src/csrd_utils/v2/templates/augments/jwt-auth-provider/dependencies/db.py → csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-provider/dependencies/db.py.template +2 -2
  14. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-provider/models/auth.py +39 -0
  15. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-provider/models/jwks.py +11 -0
  16. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-provider/models/users.py +26 -0
  17. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/augments/jwt-auth-provider/repositories/user_repository.py +42 -34
  18. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/augments/jwt-auth-provider/views/auth_view.py +24 -62
  19. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/augments/jwt-auth-provider/views/jwks_view.py +3 -12
  20. csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-provider/views/users_view.py +44 -0
  21. csrd_utils-0.3.95/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +27 -0
  22. csrd_utils-0.3.95/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/models/__init__.py +1 -0
  23. csrd_utils-0.3.95/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/models/health.py +10 -0
  24. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/requirements.txt +1 -0
  25. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/settings.py +1 -2
  26. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/views/health_view.py +2 -7
  27. csrd_utils-0.3.93/src/csrd_utils/v2/templates/augments/jwt-auth-provider/views/users_view.py +0 -101
  28. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/.gitignore +0 -0
  29. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/README.md +0 -0
  30. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/__init__.py +0 -0
  31. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/__main__.py +0 -0
  32. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/audit.py +0 -0
  33. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/augmentor.py +0 -0
  34. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/cluster.py +0 -0
  35. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/doctor.py +0 -0
  36. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  37. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  38. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  39. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  40. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  41. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  42. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  43. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  44. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  45. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  46. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  47. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  48. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  49. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  50. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  51. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  52. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  53. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  54. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  55. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  56. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  57. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  58. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  59. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  60. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  61. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  62. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  63. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  64. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  65. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  66. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  67. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  68. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  69. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  70. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  71. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/fragments.py +0 -0
  72. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/generator.py +0 -0
  73. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/resources.py +0 -0
  74. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  75. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  76. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
  77. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
  78. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  79. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  80. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  81. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  82. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
  83. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  84. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
  85. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  86. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_service.py +0 -0
  87. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
  88. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  89. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
  90. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
  91. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
  92. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
  93. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  94. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  95. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  96. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  97. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
  98. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/__init__.py +0 -0
  99. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
  100. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
  101. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +0 -0
  102. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/__init__.py +0 -0
  103. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +0 -0
  104. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
  105. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
  106. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +0 -0
  107. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +0 -0
  108. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  109. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  110. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  111. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  112. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
  113. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/tasks.py +0 -0
  114. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/cookiecutter.json +0 -0
  115. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  116. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  117. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  118. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  119. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  120. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/auth_delegate.py +0 -0
  121. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/delegates/identity_delegate.py +0 -0
  122. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  123. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_passthrough.py +0 -0
  124. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/auth_service.py +0 -0
  125. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  126. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  127. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/auth_service.py +0 -0
  128. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/models/items.py +0 -0
  129. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  130. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  131. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  132. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  133. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  134. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  135. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/auth_service_view.py +0 -0
  136. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  137. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-app-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  138. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/cookiecutter.json +0 -0
  139. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  140. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  141. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  142. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  143. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  144. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  145. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/models/auth.py +0 -0
  146. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  147. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/repositories/token_repository.py +0 -0
  148. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  149. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  150. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  151. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  152. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/auth_view.py +0 -0
  153. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-auth-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  154. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/cookiecutter.json +0 -0
  155. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  156. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  157. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  158. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  159. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  160. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  161. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/models/users.py +0 -0
  162. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  163. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/repositories/user_repository.py +0 -0
  164. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  165. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  166. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  167. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  168. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  169. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/templates/platform-identity-service/{{cookiecutter.__service_name_snake}}/views/unversioned/users_view.py +0 -0
  170. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/AGENTS.md +0 -0
  171. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/__init__.py +0 -0
  172. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/__init__.py +0 -0
  173. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/git.py +0 -0
  174. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/infra.py +0 -0
  175. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/loader.py +0 -0
  176. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/operations.py +0 -0
  177. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/presets.py +0 -0
  178. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/compose/yaml_editor.py +0 -0
  179. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/generate/__init__.py +0 -0
  180. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/generate/handlers.py +0 -0
  181. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/generate/helpers.py +0 -0
  182. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/generate/menu.py +0 -0
  183. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/models/__init__.py +0 -0
  184. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/models/base.py +0 -0
  185. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/models/spec.py +0 -0
  186. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/models/types.py +0 -0
  187. /csrd_utils-0.3.93/src/csrd_utils/v2/templates/augments/db-config/migrations.py → /csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/db-config/migrations.py.template +0 -0
  188. /csrd_utils-0.3.93/src/csrd_utils/v2/templates/augments/jwt-auth-provider/migrations.py → /csrd_utils-0.3.95/src/csrd_utils/v2/templates/augments/jwt-auth-provider/migrations.py.template +0 -0
  189. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/cookiecutter.json +0 -0
  190. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  191. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  192. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  193. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
  194. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
  195. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/tests/test_health.py +0 -0
  196. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/csrd_utils/v2/templates/service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  197. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/__init__.py +0 -0
  198. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/exceptions.py +0 -0
  199. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/menu.py +0 -0
  200. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/models.py +0 -0
  201. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/prompts.py +0 -0
  202. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/terminal.py +0 -0
  203. {csrd_utils-0.3.93 → csrd_utils-0.3.95}/src/tui_wizard/wizard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csrd-utils
3
- Version: 0.3.93
3
+ Version: 0.3.95
4
4
  Summary: CLI utilities for csrd service generation and feature augmentation
5
5
  Project-URL: Repository, https://github.com/csrd-api/fastapi-common
6
6
  Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/utils
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "csrd-utils"
3
- version = "0.3.93"
3
+ version = "0.3.95"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -131,6 +131,15 @@ AUGMENT_REGISTRY: dict[str, AugmentDescriptor] = {
131
131
  template_dir="jwt-auth-consumer",
132
132
  # compose_env, compose_depends_on, settings_fields resolved from spec
133
133
  ),
134
+ "auth-passthrough": AugmentDescriptor(
135
+ name="auth-passthrough",
136
+ scope="service",
137
+ description="Unguarded signup/login proxy endpoints to the auth service",
138
+ requires_workspace_augment="jwt-auth-provider",
139
+ packages=["csrd-delegate", "csrd-models"],
140
+ template_dir="auth-passthrough",
141
+ # compose_env, compose_depends_on, settings_fields resolved from spec
142
+ ),
134
143
  "context-logging": AugmentDescriptor(
135
144
  name="context-logging",
136
145
  scope="service",
@@ -103,6 +103,18 @@ def _wire_augment(
103
103
  env["AUTH_JWKS_URL"] = f"http://{auth_svc.name}:{auth_svc.port}/.well-known/jwks.json"
104
104
  deps[auth_svc.name] = {"condition": "service_started"}
105
105
 
106
+ # ── Dynamic: auth-passthrough ────────────────────────────────────
107
+ #
108
+ # Resolve the auth service URL for signup/login proxy endpoints.
109
+ if augment.name == "auth-passthrough":
110
+ auth_svc = next(
111
+ (s for s in spec.services if s.role == "auth"),
112
+ None,
113
+ )
114
+ if auth_svc is not None and auth_svc.name != svc.name:
115
+ env["AUTH_SERVICE_URL"] = f"http://{auth_svc.name}:{auth_svc.port}"
116
+ deps[auth_svc.name] = {"condition": "service_started"}
117
+
106
118
  # ── Dynamic: delegate-config ────────────────────────────────────
107
119
  #
108
120
  # Resolve target service URLs from the spec and inject per-target
@@ -189,6 +189,16 @@ def _augment_template_vars(
189
189
  variables["auth_service_name"] = auth_svc.name
190
190
  variables["auth_service_port"] = str(auth_svc.port)
191
191
 
192
+ # Dynamic: auth-passthrough — resolve auth service URL
193
+ if augment.name == "auth-passthrough":
194
+ auth_svc = next(
195
+ (s for s in spec.services if s.role == "auth"),
196
+ None,
197
+ )
198
+ if auth_svc is not None:
199
+ variables["auth_service_name"] = auth_svc.name
200
+ variables["auth_service_port"] = str(auth_svc.port)
201
+
192
202
  # Dynamic: delegate-config — resolve targets
193
203
  if augment.name == "delegate-config":
194
204
  targets = augment.options.get("targets", [])
@@ -250,6 +260,10 @@ def scaffold_augment(
250
260
  relative = template_file.relative_to(template_dir)
251
261
  dest = svc_dest / relative
252
262
 
263
+ # Strip .template suffix so e.g. db.py.template → db.py
264
+ if dest.name.endswith(".template"):
265
+ dest = dest.with_name(dest.name.removesuffix(".template"))
266
+
253
267
  # Never overwrite existing files
254
268
  if dest.exists():
255
269
  continue
@@ -29,6 +29,7 @@ _BASE_PACKAGES: list[str] = [
29
29
  "fastapi>=0.115,<1",
30
30
  "uvicorn[standard]>=0.30,<1",
31
31
  "pydantic-settings>=2.6,<3",
32
+ "csrd-models>=0.3,<1",
32
33
  "csrd-versioning>=0.1.36,<1",
33
34
  ]
34
35
 
@@ -154,6 +155,15 @@ def _augment_settings_fields(
154
155
  augment_fields["jwt_issuer"] = '"csrd-auth"'
155
156
  augment_fields["jwt_audience"] = f'"{spec.workspace.name}"'
156
157
 
158
+ # Dynamic: auth-passthrough
159
+ if augment.name == "auth-passthrough":
160
+ auth_svc = next(
161
+ (s for s in spec.services if s.role == "auth"),
162
+ None,
163
+ )
164
+ if auth_svc is not None:
165
+ augment_fields["auth_service_url"] = f'"http://{auth_svc.name}:{auth_svc.port}"'
166
+
157
167
  # Dynamic: delegate-config
158
168
  if augment.name == "delegate-config":
159
169
  targets = augment.options.get("targets", [])
@@ -202,13 +212,10 @@ def render_settings(svc: ServiceNode, spec: ComposeSpec) -> str:
202
212
  augment_fields = _augment_settings_fields(svc, spec)
203
213
 
204
214
  lines = [
205
- "from pydantic_settings import BaseSettings, SettingsConfigDict",
215
+ "from csrd.models import BaseSettings",
206
216
  "",
207
217
  "",
208
218
  "class Settings(BaseSettings):",
209
- " model_config = SettingsConfigDict("
210
- 'env_file=".env", env_file_encoding="utf-8", extra="ignore"'
211
- ")",
212
219
  "",
213
220
  f' app_name: str = "{svc.name}"',
214
221
  f" port: int = {svc.port}",
@@ -281,6 +288,10 @@ def render_init(svc: ServiceNode, spec: ComposeSpec) -> str:
281
288
  imports.append("")
282
289
  imports.append("from csrd.context.middleware import RequestContextMiddleware")
283
290
 
291
+ if "auth-passthrough" in augment_names:
292
+ imports.append("")
293
+ imports.append("from .views.auth_passthrough_view import router as auth_passthrough_router")
294
+
284
295
  if "service-layer" in augment_names:
285
296
  imports.append("")
286
297
  imports.append("from csrd.service import service_exception_handler")
@@ -298,12 +309,17 @@ def render_init(svc: ServiceNode, spec: ComposeSpec) -> str:
298
309
  body.append(" unversioned_app.include_router(jwks_router)")
299
310
  body.append("")
300
311
 
312
+ if "auth-passthrough" in augment_names:
313
+ body.append(" unversioned_app.include_router(auth_passthrough_router)")
314
+ body.append("")
315
+
301
316
  body.extend(
302
317
  [
303
318
  " app = compose_versioned_apps(",
304
319
  " version_mapping={UNVERSIONED: unversioned_app},",
305
320
  " config=VersionedAppComposeConfig(",
306
321
  f' title="{svc.name}",',
322
+ ' app_state={"settings": settings},',
307
323
  " api=VersionedApiConfig(",
308
324
  ' prefix="/",',
309
325
  " include_actuator_endpoints=settings.include_actuator_endpoints,",
@@ -0,0 +1,35 @@
1
+ """Auth service delegate for signup and login.
2
+
3
+ Proxies requests to the auth service's API endpoints.
4
+ This file is scaffolded once and never overwritten.
5
+ """
6
+
7
+ from csrd.delegate import BaseDelegate
8
+
9
+ from ..models.auth_passthrough import TokenResponse, UserResponse
10
+
11
+
12
+ class AuthDelegate(BaseDelegate):
13
+ """Delegate for the cluster auth service."""
14
+
15
+ def __init__(self, base_url: str) -> None:
16
+ super().__init__(base_url)
17
+
18
+ async def signup(self, username: str, password: str) -> UserResponse:
19
+ """Create a new user account via the auth service."""
20
+ return await self.post(
21
+ "/api/signup",
22
+ json={"username": username, "password": password},
23
+ response_model=UserResponse,
24
+ )
25
+
26
+ async def login(self, username: str, password: str) -> TokenResponse:
27
+ """Authenticate and obtain a JWT token via the auth service."""
28
+ return await self.post(
29
+ "/api/token",
30
+ json={"username": username, "password": password},
31
+ response_model=TokenResponse,
32
+ )
33
+
34
+
35
+ __all__ = ("AuthDelegate",)
@@ -0,0 +1,21 @@
1
+ """FastAPI dependency for the auth passthrough delegate.
2
+
3
+ This file is scaffolded once and never overwritten.
4
+ """
5
+
6
+ from typing import Annotated
7
+
8
+ from fastapi import Depends
9
+
10
+ from ..delegates.auth_delegate import AuthDelegate
11
+ from . import Settings
12
+
13
+
14
+ def auth_delegate_factory(settings: Settings) -> AuthDelegate:
15
+ """Create an ``AuthDelegate`` bound to the configured auth service URL."""
16
+ return AuthDelegate(settings.auth_service_url)
17
+
18
+
19
+ AuthDelegateDep = Annotated[AuthDelegate, Depends(auth_delegate_factory)]
20
+
21
+ __all__ = ("AuthDelegateDep", "auth_delegate_factory")
@@ -0,0 +1,32 @@
1
+ """Auth passthrough request/response models."""
2
+
3
+ from csrd.models import BaseModel
4
+
5
+
6
+ class SignupRequest(BaseModel):
7
+ """Signup request body."""
8
+
9
+ username: str
10
+ password: str
11
+
12
+
13
+ class LoginRequest(BaseModel):
14
+ """Login request body."""
15
+
16
+ username: str
17
+ password: str
18
+
19
+
20
+ class TokenResponse(BaseModel):
21
+ """Token response from the auth service."""
22
+
23
+ access_token: str
24
+ token_type: str = "bearer"
25
+ expires_in: int
26
+
27
+
28
+ class UserResponse(BaseModel):
29
+ """User response from the auth service."""
30
+
31
+ id: str
32
+ username: str
@@ -0,0 +1,25 @@
1
+ """Auth passthrough endpoints — unguarded signup and login.
2
+
3
+ Proxies signup and login requests to the cluster auth service.
4
+ No authentication required on these endpoints.
5
+ This file is scaffolded once and never overwritten.
6
+ """
7
+
8
+ from fastapi import APIRouter
9
+
10
+ from ..dependencies.auth_passthrough import AuthDelegateDep
11
+ from ..models.auth_passthrough import LoginRequest, SignupRequest, TokenResponse, UserResponse
12
+
13
+ router = APIRouter(prefix="/api/auth", tags=["Auth"])
14
+
15
+
16
+ @router.post("/signup", status_code=201, response_model=UserResponse)
17
+ async def signup(body: SignupRequest, delegate: AuthDelegateDep) -> UserResponse:
18
+ """Create a new user account via the auth service."""
19
+ return await delegate.signup(body.username, body.password)
20
+
21
+
22
+ @router.post("/login", response_model=TokenResponse)
23
+ async def login(body: LoginRequest, delegate: AuthDelegateDep) -> TokenResponse:
24
+ """Authenticate and obtain a JWT token via the auth service."""
25
+ return await delegate.login(body.username, body.password)
@@ -1,15 +1,19 @@
1
- """Database adapter factory for $service_name.
1
+ """Database adapter dependency for $service_name.
2
2
 
3
3
  This file is scaffolded once and never overwritten. Customize the
4
4
  adapter type, DSN construction, or add connection pooling here.
5
5
  """
6
6
 
7
+ from typing import Annotated
8
+
9
+ from fastapi import Depends
10
+
7
11
  $db_adapter_import
8
12
 
9
- from ..settings import settings
13
+ from . import Settings
10
14
 
11
15
 
12
- def create_adapter() -> $db_adapter_class:
16
+ def _create_adapter(settings: Settings) -> $db_adapter_class:
13
17
  """Create the database adapter for this service.
14
18
 
15
19
  Override this to switch adapter types (PGAdapter, MariaAdapter)
@@ -18,4 +22,6 @@ def create_adapter() -> $db_adapter_class:
18
22
  return $db_adapter_constructor
19
23
 
20
24
 
21
- __all__ = ("create_adapter",)
25
+ Adapter = Annotated[$db_adapter_class, Depends(_create_adapter)]
26
+
27
+ __all__ = ("Adapter",)
@@ -13,6 +13,11 @@ from ..settings import settings
13
13
 
14
14
  # Build the auth dependency — tokens are verified locally using
15
15
  # the public keys from the auth service's JWKS endpoint.
16
+ #
17
+ # Note: this is constructed at module level because FastAPI security
18
+ # dependencies (Security / Depends) must be stable objects. The
19
+ # settings singleton is appropriate here — auth config is fixed
20
+ # for the lifetime of the process.
16
21
  auth_dep = create_jwt_bearer(
17
22
  key=JWKSKeyProvider(url=settings.auth_jwks_url),
18
23
  algorithms=[settings.jwt_algorithm],
@@ -5,12 +5,12 @@ This file is scaffolded once and never overwritten.
5
5
 
6
6
  $db_adapter_import
7
7
 
8
- from ..settings import settings
8
+ from . import Settings
9
9
 
10
10
  _adapter: $db_adapter_class | None = None
11
11
 
12
12
 
13
- async def get_adapter() -> $db_adapter_class:
13
+ async def get_adapter(settings: Settings) -> $db_adapter_class:
14
14
  """Get or create the database adapter (singleton per process)."""
15
15
  global _adapter
16
16
  if _adapter is None:
@@ -0,0 +1,39 @@
1
+ """Token issuance and verification models."""
2
+
3
+ from csrd.models import BaseModel
4
+
5
+
6
+ class TokenRequest(BaseModel):
7
+ username: str
8
+ password: str
9
+
10
+
11
+ class TokenResponse(BaseModel):
12
+ """JWT token issuance response."""
13
+
14
+ access_token: str
15
+ token_type: str = "bearer"
16
+ expires_in: int
17
+
18
+
19
+ class VerifyRequest(BaseModel):
20
+ token: str
21
+
22
+
23
+ class VerifyClaims(BaseModel):
24
+ """JWT claim fields returned by the verify endpoint."""
25
+
26
+ sub: str
27
+ user_name: str
28
+ authorities: list[str]
29
+ iat: int
30
+ exp: int
31
+ iss: str
32
+ aud: str
33
+
34
+
35
+ class VerifyResponse(BaseModel):
36
+ """Token verification response."""
37
+
38
+ active: bool
39
+ claims: VerifyClaims
@@ -0,0 +1,11 @@
1
+ """JWKS response models."""
2
+
3
+ from typing import Any
4
+
5
+ from csrd.models import BaseModel
6
+
7
+
8
+ class JWKSResponse(BaseModel):
9
+ """JSON Web Key Set response (RFC 7517)."""
10
+
11
+ keys: list[dict[str, Any]]
@@ -0,0 +1,26 @@
1
+ """User management models."""
2
+
3
+ from csrd.models import BaseModel
4
+
5
+
6
+ class SignupRequest(BaseModel):
7
+ username: str
8
+ password: str
9
+
10
+
11
+ class UserResponse(BaseModel):
12
+ """Basic user info returned after signup or lookup."""
13
+
14
+ id: str
15
+ username: str
16
+
17
+
18
+ class UserRolesResponse(BaseModel):
19
+ """User with their assigned roles/authorities."""
20
+
21
+ user_id: str
22
+ roles: list[str]
23
+
24
+
25
+ class RoleRequest(BaseModel):
26
+ role: str
@@ -7,85 +7,93 @@ This file is scaffolded once and never overwritten.
7
7
  import hashlib
8
8
  import secrets
9
9
  import uuid
10
- from typing import Any
11
10
 
11
+ from fastapi import HTTPException
12
12
 
13
- class UserRepository:
14
- """User, credential, and role storage backed by a database adapter."""
13
+ from csrd.repository import BaseRepository
14
+
15
+ from ..models.users import UserResponse, UserRolesResponse
15
16
 
16
- def __init__(self, adapter: Any) -> None:
17
- self._adapter = adapter
17
+
18
+ class UserRepository(BaseRepository):
19
+ """User, credential, and role storage backed by a database adapter."""
18
20
 
19
21
  # ── User CRUD ────────────────────────────────────────────────────
20
22
 
21
- async def create_user(self, username: str, password: str) -> dict:
23
+ async def create_user(self, username: str, password: str) -> UserResponse:
22
24
  """Create a new user with hashed credentials."""
23
25
  user_id = str(uuid.uuid4())
24
- await self._adapter.execute(
26
+ await self.execute(
25
27
  "INSERT INTO users (id, username) VALUES (:id, :username)",
26
28
  {"id": user_id, "username": username},
27
29
  )
28
30
 
29
31
  password_hash = self._hash_password(password)
30
- await self._adapter.execute(
32
+ await self.execute(
31
33
  "INSERT INTO user_credentials (user_id, password_hash) "
32
34
  "VALUES (:user_id, :password_hash)",
33
35
  {"user_id": user_id, "password_hash": password_hash},
34
36
  )
35
37
 
36
- return {"id": user_id, "username": username}
38
+ return UserResponse(id=user_id, username=username)
37
39
 
38
- async def get_user(self, user_id: str) -> dict | None:
39
- """Get a user by ID."""
40
- return await self._adapter.fetch_one(
41
- "SELECT id, username, created_at FROM users WHERE id = :id",
40
+ async def get_user(self, user_id: str) -> UserResponse:
41
+ """Get a user by ID. Raises 404 if not found."""
42
+ return await self.require_one(
43
+ "SELECT id, username FROM users WHERE id = :id",
42
44
  {"id": user_id},
45
+ model=UserResponse,
46
+ detail="User not found",
43
47
  )
44
48
 
45
- async def get_user_by_username(self, username: str) -> dict | None:
46
- """Get a user by username."""
47
- return await self._adapter.fetch_one(
48
- "SELECT id, username, created_at FROM users WHERE username = :username",
49
+ async def get_user_by_username(self, username: str) -> UserResponse | None:
50
+ """Get a user by username, or ``None`` if not found."""
51
+ row = await self.fetch_one(
52
+ "SELECT id, username FROM users WHERE username = :username",
49
53
  {"username": username},
50
54
  )
55
+ if row is None:
56
+ return None
57
+ return self.apply_model(row, model=UserResponse)
51
58
 
52
59
  # ── Credential verification ──────────────────────────────────────
53
60
 
54
- async def verify_credentials(self, username: str, password: str) -> dict | None:
55
- """Verify username + password. Returns user dict or None."""
61
+ async def verify_credentials(self, username: str, password: str) -> UserResponse:
62
+ """Verify username + password. Raises 401 on failure."""
56
63
  user = await self.get_user_by_username(username)
57
64
  if user is None:
58
- return None
65
+ raise HTTPException(status_code=401, detail="Invalid credentials")
59
66
 
60
- cred = await self._adapter.fetch_one(
67
+ cred = await self.fetch_one(
61
68
  "SELECT password_hash FROM user_credentials WHERE user_id = :user_id",
62
- {"user_id": user["id"]},
69
+ {"user_id": user.id},
63
70
  )
64
- if cred is None:
65
- return None
66
-
67
- if not self._verify_password(password, cred["password_hash"]):
68
- return None
71
+ if cred is None or not self._verify_password(password, cred["password_hash"]):
72
+ raise HTTPException(status_code=401, detail="Invalid credentials")
69
73
 
70
74
  return user
71
75
 
72
76
  # ── Role management ──────────────────────────────────────────────
73
77
 
74
- async def get_roles(self, user_id: str) -> list[str]:
75
- """Get all roles for a user."""
76
- rows = await self._adapter.fetch_all(
78
+ async def get_user_roles(self, user_id: str) -> UserRolesResponse:
79
+ """Get all roles for a user. Raises 404 if user not found."""
80
+ await self.get_user(user_id) # ensures user exists (raises 404)
81
+ rows = await self.fetch_all(
77
82
  "SELECT role FROM user_roles WHERE user_id = :user_id",
78
83
  {"user_id": user_id},
79
84
  )
80
- return [row["role"] for row in rows]
85
+ roles = [row["role"] for row in rows]
86
+ return UserRolesResponse(user_id=user_id, roles=roles)
81
87
 
82
- async def add_role(self, user_id: str, role: str) -> None:
83
- """Add a role to a user. Idempotent."""
84
- await self._adapter.execute(
88
+ async def add_role(self, user_id: str, role: str) -> UserRolesResponse:
89
+ """Add a role to a user. Idempotent. Returns updated roles."""
90
+ await self.get_user(user_id) # ensures user exists (raises 404)
91
+ await self.execute(
85
92
  "INSERT OR IGNORE INTO user_roles (user_id, role) "
86
93
  "VALUES (:user_id, :role)",
87
94
  {"user_id": user_id, "role": role},
88
95
  )
96
+ return await self.get_user_roles(user_id)
89
97
 
90
98
  # ── Password hashing ─────────────────────────────────────────────
91
99
  #
@@ -8,72 +8,31 @@ from typing import Any
8
8
 
9
9
  import jwt as pyjwt
10
10
  from fastapi import APIRouter, HTTPException
11
- from pydantic import BaseModel
12
11
 
13
- from ..dependencies.db import get_adapter
14
- from ..repositories.user_repository import UserRepository
15
- from ..settings import settings
12
+ from ..dependencies import Settings
13
+ from ..dependencies.db import Adapter, UserRepo
14
+ from ..models.auth import (
15
+ TokenRequest,
16
+ TokenResponse,
17
+ VerifyClaims,
18
+ VerifyRequest,
19
+ VerifyResponse,
20
+ )
16
21
 
17
22
  router = APIRouter(tags=["auth"])
18
23
 
19
24
 
20
- # ---------------------------------------------------------------------------
21
- # Request / response models
22
- # ---------------------------------------------------------------------------
23
-
24
-
25
- class TokenRequest(BaseModel):
26
- username: str
27
- password: str
28
-
29
-
30
- class TokenResponse(BaseModel):
31
- """JWT token issuance response."""
32
-
33
- access_token: str
34
- token_type: str = "bearer"
35
- expires_in: int
36
-
37
-
38
- class VerifyRequest(BaseModel):
39
- token: str
40
-
41
-
42
- class VerifyClaims(BaseModel):
43
- """JWT claim fields returned by the verify endpoint."""
44
-
45
- sub: str
46
- user_name: str
47
- authorities: list[str]
48
- iat: int
49
- exp: int
50
- iss: str
51
- aud: str
52
-
53
-
54
- class VerifyResponse(BaseModel):
55
- """Token verification response."""
56
-
57
- active: bool
58
- claims: VerifyClaims
59
-
60
-
61
- # ---------------------------------------------------------------------------
62
- # Endpoints
63
- # ---------------------------------------------------------------------------
64
-
65
25
 
66
26
  @router.post("/api/token", response_model=TokenResponse)
67
- async def issue_token(body: TokenRequest) -> TokenResponse:
27
+ async def issue_token(
28
+ body: TokenRequest,
29
+ settings: Settings,
30
+ adapter: Adapter,
31
+ repo: UserRepo,
32
+ ) -> TokenResponse:
68
33
  """Validate credentials and issue a JWT access token."""
69
- adapter = await get_adapter()
70
- repo = UserRepository(adapter)
71
-
72
34
  user = await repo.verify_credentials(body.username, body.password)
73
- if user is None:
74
- raise HTTPException(status_code=401, detail="Invalid credentials")
75
-
76
- roles = await repo.get_roles(user["id"])
35
+ roles_resp = await repo.get_user_roles(user.id)
77
36
 
78
37
  # Get the active signing key
79
38
  from csrd.auth import KeyRingManager
@@ -91,9 +50,9 @@ async def issue_token(body: TokenRequest) -> TokenResponse:
91
50
 
92
51
  now = int(time.time())
93
52
  payload: dict[str, Any] = {
94
- "sub": user["id"],
95
- "user_name": user["username"],
96
- "authorities": roles,
53
+ "sub": user.id,
54
+ "user_name": user.username,
55
+ "authorities": roles_resp.roles,
97
56
  "iat": now,
98
57
  "exp": now + settings.jwt_ttl_seconds,
99
58
  "iss": settings.jwt_issuer,
@@ -111,9 +70,12 @@ async def issue_token(body: TokenRequest) -> TokenResponse:
111
70
 
112
71
 
113
72
  @router.post("/api/verify", response_model=VerifyResponse)
114
- async def verify_token(body: VerifyRequest) -> VerifyResponse:
73
+ async def verify_token(
74
+ body: VerifyRequest,
75
+ settings: Settings,
76
+ adapter: Adapter,
77
+ ) -> VerifyResponse:
115
78
  """Verify a JWT and return its claims."""
116
- adapter = await get_adapter()
117
79
 
118
80
  from csrd.auth import KeyRingManager
119
81