tina4-python 3.13.19__tar.gz → 3.13.21__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 (157) hide show
  1. {tina4_python-3.13.19 → tina4_python-3.13.21}/PKG-INFO +1 -1
  2. {tina4_python-3.13.19 → tina4_python-3.13.21}/pyproject.toml +1 -1
  3. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/CLAUDE.md +2 -2
  4. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/__init__.py +1 -1
  5. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/auth/__init__.py +42 -20
  6. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/middleware.py +4 -4
  7. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/frond/engine.py +2 -2
  8. {tina4_python-3.13.19 → tina4_python-3.13.21}/.gitignore +0 -0
  9. {tina4_python-3.13.19 → tina4_python-3.13.21}/README.md +0 -0
  10. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/HtmlElement.py +0 -0
  11. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/Testing.py +0 -0
  12. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/ai/__init__.py +0 -0
  13. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/api/__init__.py +0 -0
  14. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/cache/__init__.py +0 -0
  15. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/cli/__init__.py +0 -0
  16. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/container/__init__.py +0 -0
  17. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/__init__.py +0 -0
  18. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/cache.py +0 -0
  19. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/constants.py +0 -0
  20. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/events.py +0 -0
  21. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/rate_limiter.py +0 -0
  22. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/core/server.py +0 -0
  26. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/crud/__init__.py +0 -0
  27. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/__init__.py +0 -0
  28. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/adapter.py +0 -0
  29. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/connection.py +0 -0
  30. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/firebird.py +0 -0
  31. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/mongodb.py +0 -0
  32. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/mssql.py +0 -0
  33. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/mysql.py +0 -0
  34. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/odbc.py +0 -0
  35. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/postgres.py +0 -0
  36. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/database/sqlite.py +0 -0
  37. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/debug/__init__.py +0 -0
  38. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/debug/error_overlay.py +0 -0
  39. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/dev_admin/__init__.py +0 -0
  40. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/dev_admin/metrics.py +0 -0
  41. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/dev_admin/plan.py +0 -0
  42. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/dev_admin/project_index.py +0 -0
  43. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/docs.py +0 -0
  44. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/dotenv/__init__.py +0 -0
  45. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/env.py +0 -0
  46. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/frond/FROND.md +0 -0
  47. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/frond/__init__.py +0 -0
  48. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/auth/meta.json +0 -0
  49. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  50. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/database/meta.json +0 -0
  51. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  52. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/error-overlay/meta.json +0 -0
  53. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  54. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/orm/meta.json +0 -0
  55. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  56. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  57. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/queue/meta.json +0 -0
  58. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  59. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/rest-api/meta.json +0 -0
  60. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  61. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/templates/meta.json +0 -0
  62. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  63. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  64. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/graphql/__init__.py +0 -0
  65. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/i18n/__init__.py +0 -0
  66. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/mcp/__init__.py +0 -0
  67. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/mcp/protocol.py +0 -0
  68. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/mcp/tools.py +0 -0
  69. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/messenger/__init__.py +0 -0
  70. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/migration/__init__.py +0 -0
  71. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/migration/runner.py +0 -0
  72. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/orm/__init__.py +0 -0
  73. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/orm/fields.py +0 -0
  74. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/orm/model.py +0 -0
  75. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/__feedback/widget.js +0 -0
  76. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/css/tina4.css +0 -0
  77. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/css/tina4.min.css +0 -0
  78. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/favicon.ico +0 -0
  79. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/images/logo.svg +0 -0
  80. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  81. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/js/frond.js +0 -0
  82. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/js/frond.min.js +0 -0
  83. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  84. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  85. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/js/tina4.min.js +0 -0
  86. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/js/tina4js.min.js +0 -0
  87. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/swagger/index.html +0 -0
  88. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  89. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/query_builder/__init__.py +0 -0
  90. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue/__init__.py +0 -0
  91. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue/job.py +0 -0
  92. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue/kafka_backend.py +0 -0
  93. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue/lite_backend.py +0 -0
  94. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue/mongo_backend.py +0 -0
  95. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue/rabbitmq_backend.py +0 -0
  96. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue_backends/__init__.py +0 -0
  97. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue_backends/kafka_backend.py +0 -0
  98. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue_backends/mongo_backend.py +0 -0
  99. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  100. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/__init__.py +0 -0
  101. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  102. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_badges.scss +0 -0
  103. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  104. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_cards.scss +0 -0
  105. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_forms.scss +0 -0
  106. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_grid.scss +0 -0
  107. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_modals.scss +0 -0
  108. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_nav.scss +0 -0
  109. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_reset.scss +0 -0
  110. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_tables.scss +0 -0
  111. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_typography.scss +0 -0
  112. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  113. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/_variables.scss +0 -0
  114. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/base.scss +0 -0
  115. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/colors.scss +0 -0
  116. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/scss/tina4css/tina4.scss +0 -0
  117. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/seeder/__init__.py +0 -0
  118. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/service/__init__.py +0 -0
  119. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/session/__init__.py +0 -0
  120. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/session_handlers/__init__.py +0 -0
  121. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  122. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/session_handlers/redis_handler.py +0 -0
  123. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/session_handlers/valkey_handler.py +0 -0
  124. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/swagger/__init__.py +0 -0
  125. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/components/crud.twig +0 -0
  126. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  127. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  128. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/docker/python/Dockerfile +0 -0
  129. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  130. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/302.twig +0 -0
  131. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/401.twig +0 -0
  132. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/403.twig +0 -0
  133. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/404.twig +0 -0
  134. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/500.twig +0 -0
  135. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/502.twig +0 -0
  136. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/503.twig +0 -0
  137. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/errors/base.twig +0 -0
  138. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/frontend/README.md +0 -0
  139. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/templates/readme.md +0 -0
  140. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/test/__init__.py +0 -0
  141. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/test_client/__init__.py +0 -0
  142. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  143. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  144. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  145. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  146. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  147. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  148. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  149. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  150. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  151. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  152. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  153. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  154. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/validator/__init__.py +0 -0
  155. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/websocket/__init__.py +0 -0
  156. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/websocket/backplane.py +0 -0
  157. {tina4_python-3.13.19 → tina4_python-3.13.21}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.13.19
3
+ Version: 3.13.21
4
4
  Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.19"
3
+ version = "3.13.21"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -1590,7 +1590,7 @@ Key `.env` settings:
1590
1590
 
1591
1591
  ```bash
1592
1592
  # Authentication
1593
- SECRET=your-jwt-secret # JWT signing (default uses insecure placeholder)
1593
+ TINA4_SECRET=your-jwt-secret # JWT signing (warns + uses a blank, insecure secret if unset)
1594
1594
  TINA4_API_KEY=your-api-key # Static bearer token for API auth (API_KEY fallback supported)
1595
1595
  TINA4_TOKEN_LIMIT=60 # Token lifetime in minutes (default: 60)
1596
1596
 
@@ -1836,7 +1836,7 @@ async def dashboard(request, response):
1836
1836
  ## v3 Features Summary
1837
1837
 
1838
1838
  - **55 built-in features**, zero third-party dependencies
1839
- - **2,852 tests** passing across all modules
1839
+ - **2,857 tests** passing across all modules
1840
1840
  - **Production server auto-detect**: `tina4python serve --production` auto-installs uvicorn
1841
1841
  - **`tina4python generate`**: model, route, migration, middleware scaffolding
1842
1842
  - **Database**: 5 engines (SQLite, PostgreSQL, MySQL, MSSQL, Firebird), query caching (`TINA4_DB_CACHE=true`, `cache_stats()`, `cache_clear()`)
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.13.19"
11
+ __version__ = "3.13.21"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -29,7 +29,7 @@ import functools
29
29
  class _DualMethod:
30
30
  """Descriptor that makes a method callable on both the class and an instance.
31
31
 
32
- - ``Auth.get_token(payload)`` — creates a temporary Auth() using env SECRET
32
+ - ``Auth.get_token(payload)`` — creates a temporary Auth() using env TINA4_SECRET
33
33
  - ``auth.get_token(payload)`` — uses the instance's own secret / settings
34
34
  """
35
35
 
@@ -42,13 +42,35 @@ class _DualMethod:
42
42
  # Called on the class: Auth.get_token(payload, ...)
43
43
  @functools.wraps(self._func)
44
44
  def _class_call(*args, **kwargs):
45
- inst = cls() # Auth() reads SECRET from env
45
+ inst = cls() # Auth() reads TINA4_SECRET from env
46
46
  return self._func(inst, *args, **kwargs)
47
47
  return _class_call
48
48
  # Called on an instance: auth_instance.get_token(payload, ...)
49
49
  return functools.partial(self._func, obj)
50
50
 
51
51
 
52
+ def _resolve_secret(secret: str = None) -> str:
53
+ """Resolve the JWT signing secret.
54
+
55
+ Reads ``TINA4_SECRET`` from the environment. When neither an explicit
56
+ secret nor ``TINA4_SECRET`` is set, warns loudly and returns a blank
57
+ secret — parity with the PHP/Node frameworks. Tina4 never silently signs
58
+ with a guessable built-in default, which would make tokens forgeable.
59
+ """
60
+ if secret:
61
+ return secret
62
+ env_secret = os.environ.get("TINA4_SECRET", "")
63
+ if not env_secret:
64
+ try:
65
+ from tina4_python.debug import Log
66
+ Log.warning("Auth: TINA4_SECRET not set in .env — using blank secret (insecure)")
67
+ except Exception:
68
+ import sys
69
+ print("Auth: TINA4_SECRET not set in .env — using blank secret (insecure)", file=sys.stderr)
70
+ return ""
71
+ return env_secret
72
+
73
+
52
74
  class Auth:
53
75
  """JWT authentication and password hashing — zero dependencies."""
54
76
 
@@ -56,11 +78,11 @@ class Auth:
56
78
  expires_in: int = None):
57
79
  """
58
80
  Args:
59
- secret: Signing secret (falls back to SECRET env var).
81
+ secret: Signing secret (falls back to the TINA4_SECRET env var).
60
82
  algorithm: JWT algorithm (default HS256).
61
83
  expires_in: Token lifetime in seconds (default 3600).
62
84
  """
63
- self.secret = secret or os.environ.get("TINA4_SECRET", "tina4-default-secret")
85
+ self.secret = secret if secret is not None else _resolve_secret()
64
86
  self.algorithm = algorithm
65
87
  # JWT expiry env var:
66
88
  # - TINA4_TOKEN_EXPIRES_IN (preferred — matches docs and the
@@ -80,7 +102,7 @@ class Auth:
80
102
 
81
103
  Args:
82
104
  expires_in: Lifetime in minutes (default: self.expires_in).
83
- secret: Override signing secret (default: self.secret / SECRET env var).
105
+ secret: Override signing secret (default: self.secret / TINA4_SECRET env var).
84
106
 
85
107
  Returns: header.payload.signature
86
108
  """
@@ -176,36 +198,36 @@ class Auth:
176
198
 
177
199
  # get_token and valid_token are the primary names
178
200
 
179
- # ── Class-level convenience methods (read SECRET from env) ────
201
+ # ── Class-level convenience methods (read TINA4_SECRET from env) ────
180
202
 
181
203
  @classmethod
182
204
  def get_token_static(cls, payload: dict, expires_in: int = 60) -> str:
183
- """Create a JWT without instantiating Auth — reads SECRET from env."""
184
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
205
+ """Create a JWT without instantiating Auth — reads TINA4_SECRET from env."""
206
+ secret = _resolve_secret()
185
207
  auth = cls(secret=secret, expires_in=expires_in)
186
208
  return auth.get_token(payload)
187
209
 
188
210
  @classmethod
189
211
  def valid_token_static(cls, token: str) -> "dict | None":
190
- """Validate a JWT without instantiating Auth — reads SECRET from env.
212
+ """Validate a JWT without instantiating Auth — reads TINA4_SECRET from env.
191
213
 
192
214
  Returns the decoded payload on success, ``None`` on failure.
193
215
  """
194
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
216
+ secret = _resolve_secret()
195
217
  auth = cls(secret=secret)
196
218
  return auth.valid_token(token)
197
219
 
198
220
  @classmethod
199
221
  def get_payload_static(cls, token: str) -> dict | None:
200
222
  """Decode payload (no validation) without instantiating Auth."""
201
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
223
+ secret = _resolve_secret()
202
224
  auth = cls(secret=secret)
203
225
  return auth.get_payload(token)
204
226
 
205
227
  @classmethod
206
228
  def refresh_token_static(cls, token: str, expires_in: int = 60) -> str | None:
207
- """Refresh a JWT without instantiating Auth — reads SECRET from env."""
208
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
229
+ """Refresh a JWT without instantiating Auth — reads TINA4_SECRET from env."""
230
+ secret = _resolve_secret()
209
231
  auth = cls(secret=secret, expires_in=expires_in)
210
232
  return auth.refresh_token(token)
211
233
 
@@ -213,10 +235,10 @@ class Auth:
213
235
  def authenticate_request_static(cls, headers: dict) -> dict | None:
214
236
  """Extract and validate auth from request headers without instantiating Auth.
215
237
 
216
- Reads SECRET from env. Checks: Bearer JWT, Bearer API key, Basic auth.
238
+ Reads TINA4_SECRET from env. Checks: Bearer JWT, Bearer API key, Basic auth.
217
239
  Returns payload dict on success, None on failure.
218
240
  """
219
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
241
+ secret = _resolve_secret()
220
242
  auth = cls(secret=secret)
221
243
  return auth.authenticate_request(headers)
222
244
 
@@ -281,7 +303,7 @@ class Auth:
281
303
  """Extract and validate auth from request headers.
282
304
 
283
305
  Args:
284
- secret: Override signing secret (default: self.secret / SECRET env var).
306
+ secret: Override signing secret (default: self.secret / TINA4_SECRET env var).
285
307
  algorithm: JWT algorithm override (default: "HS256").
286
308
 
287
309
  Checks: Bearer JWT, Bearer API key, Basic auth.
@@ -324,14 +346,14 @@ def _b64url_decode(s: str) -> bytes:
324
346
  # ── Module-level convenience functions (use static methods) ────
325
347
 
326
348
  def get_token(payload: dict, expires_in: int = 60, secret: str = None) -> str:
327
- """Create a JWT — reads SECRET from env. Shortcut for Auth.get_token_static()."""
349
+ """Create a JWT — reads TINA4_SECRET from env. Shortcut for Auth.get_token_static()."""
328
350
  if secret is not None:
329
351
  return Auth(secret=secret).get_token(payload, expires_in=expires_in)
330
352
  return Auth.get_token_static(payload, expires_in=expires_in)
331
353
 
332
354
 
333
355
  def valid_token(token: str) -> "dict | None":
334
- """Validate a JWT signature and expiry — reads SECRET from env.
356
+ """Validate a JWT signature and expiry — reads TINA4_SECRET from env.
335
357
 
336
358
  Returns the decoded payload dict on success, ``None`` on failure.
337
359
  The truthy/falsy split keeps legacy ``if valid_token(t):`` code working
@@ -346,12 +368,12 @@ def get_payload(token: str) -> dict | None:
346
368
 
347
369
 
348
370
  def refresh_token(token: str, expires_in: int = 60) -> str | None:
349
- """Refresh a JWT — reads SECRET from env. Shortcut for Auth.refresh_token_static()."""
371
+ """Refresh a JWT — reads TINA4_SECRET from env. Shortcut for Auth.refresh_token_static()."""
350
372
  return Auth.refresh_token_static(token, expires_in=expires_in)
351
373
 
352
374
 
353
375
  def authenticate_request(headers: dict, secret: str = None, algorithm: str = "HS256") -> dict | None:
354
- """Validate auth from request headers — reads SECRET from env."""
376
+ """Validate auth from request headers — reads TINA4_SECRET from env."""
355
377
  if secret is not None:
356
378
  return Auth(secret=secret).authenticate_request(headers, algorithm=algorithm)
357
379
  return Auth.authenticate_request_static(headers)
@@ -286,8 +286,8 @@ class CsrfMiddleware:
286
286
  if auth_header.startswith("Bearer "):
287
287
  bearer_token = auth_header[7:].strip()
288
288
  if bearer_token:
289
- from tina4_python.auth import Auth as _CsrfAuth
290
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
289
+ from tina4_python.auth import Auth as _CsrfAuth, _resolve_secret
290
+ secret = _resolve_secret()
291
291
  auth = _CsrfAuth(secret=secret)
292
292
  if auth.valid_token(bearer_token):
293
293
  return request, response
@@ -325,8 +325,8 @@ class CsrfMiddleware:
325
325
  )
326
326
 
327
327
  # Validate the token
328
- from tina4_python.auth import Auth as _CsrfAuth
329
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
328
+ from tina4_python.auth import Auth as _CsrfAuth, _resolve_secret
329
+ secret = _resolve_secret()
330
330
  auth = _CsrfAuth(secret=secret)
331
331
  if not auth.valid_token(token):
332
332
  return request, response.error(
@@ -15,7 +15,7 @@ from functools import lru_cache
15
15
  from pathlib import Path
16
16
  from datetime import datetime
17
17
 
18
- from tina4_python.auth import Auth as _FrondAuth
18
+ from tina4_python.auth import Auth as _FrondAuth, _resolve_secret
19
19
 
20
20
 
21
21
  class SafeString(str):
@@ -1173,7 +1173,7 @@ def _generate_form_jwt(descriptor: str = "", session_id: str = "") -> str:
1173
1173
  if sid:
1174
1174
  payload["session_id"] = sid
1175
1175
 
1176
- secret = os.environ.get("TINA4_SECRET", "tina4-default-secret")
1176
+ secret = _resolve_secret()
1177
1177
  ttl = int(os.environ.get("TINA4_TOKEN_EXPIRES_IN", "60"))
1178
1178
  auth = _FrondAuth(secret=secret, expires_in=ttl)
1179
1179
  return auth.get_token(payload)
File without changes