tina4-python 3.13.91__tar.gz → 3.13.92__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 (173) hide show
  1. {tina4_python-3.13.91 → tina4_python-3.13.92}/PKG-INFO +1 -1
  2. {tina4_python-3.13.91 → tina4_python-3.13.92}/pyproject.toml +1 -1
  3. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/auth/__init__.py +68 -10
  4. {tina4_python-3.13.91 → tina4_python-3.13.92}/.gitignore +0 -0
  5. {tina4_python-3.13.91 → tina4_python-3.13.92}/README.md +0 -0
  6. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/CLAUDE.md +0 -0
  7. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/HtmlElement.py +0 -0
  8. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/Testing.py +0 -0
  9. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/__init__.py +0 -0
  10. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/cache/__init__.py +0 -0
  13. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/cli/__init__.py +0 -0
  14. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/container/__init__.py +0 -0
  15. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/context/__init__.py +0 -0
  16. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/context/chunker.py +0 -0
  17. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/__init__.py +0 -0
  18. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/cache.py +0 -0
  19. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/constants.py +0 -0
  20. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/events.py +0 -0
  21. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/middleware.py +0 -0
  22. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/rate_limiter.py +0 -0
  23. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/request.py +0 -0
  24. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/response.py +0 -0
  25. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/router.py +0 -0
  26. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/core/server.py +0 -0
  27. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/crud/__init__.py +0 -0
  28. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/__init__.py +0 -0
  29. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/adapter.py +0 -0
  30. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/connection.py +0 -0
  31. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/firebird.py +0 -0
  32. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/mongodb.py +0 -0
  33. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/mssql.py +0 -0
  34. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/mysql.py +0 -0
  35. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/odbc.py +0 -0
  36. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/postgres.py +0 -0
  37. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/database/sqlite.py +0 -0
  38. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/debug/__init__.py +0 -0
  39. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/debug/error_overlay.py +0 -0
  40. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/dev_admin/__init__.py +0 -0
  41. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/dev_admin/metrics.py +0 -0
  42. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/dev_admin/metrics_engine.py +0 -0
  43. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/dev_admin/plan.py +0 -0
  44. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/dev_admin/project_index.py +0 -0
  45. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/docs.py +0 -0
  46. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/docstore/__init__.py +0 -0
  47. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/dotenv/__init__.py +0 -0
  48. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/env.py +0 -0
  49. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/frond/FROND.md +0 -0
  50. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/frond/__init__.py +0 -0
  51. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/frond/compiler.py +0 -0
  52. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/frond/engine.py +0 -0
  53. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/frond/parser.py +0 -0
  54. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/auth/meta.json +0 -0
  55. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  56. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/database/meta.json +0 -0
  57. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  58. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/error-overlay/meta.json +0 -0
  59. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  60. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/orm/meta.json +0 -0
  61. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  62. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  63. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/queue/meta.json +0 -0
  64. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  65. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/rest-api/meta.json +0 -0
  66. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  67. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/templates/meta.json +0 -0
  68. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  69. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  70. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/graphql/__init__.py +0 -0
  71. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/i18n/__init__.py +0 -0
  72. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/mcp/__init__.py +0 -0
  73. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/mcp/protocol.py +0 -0
  74. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/mcp/tools.py +0 -0
  75. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/messenger/__init__.py +0 -0
  76. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/migration/__init__.py +0 -0
  77. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/migration/runner.py +0 -0
  78. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/mqtt/__init__.py +0 -0
  79. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/mqtt/message.py +0 -0
  80. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/orm/__init__.py +0 -0
  81. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/orm/fields.py +0 -0
  82. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/orm/model.py +0 -0
  83. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/__feedback/widget.js +0 -0
  84. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/css/tina4.css +0 -0
  85. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/css/tina4.min.css +0 -0
  86. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/favicon.ico +0 -0
  87. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/images/logo.svg +0 -0
  88. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  89. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/js/frond.js +0 -0
  90. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/js/frond.min.js +0 -0
  91. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  92. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/js/tina4.min.js +0 -0
  93. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/js/tina4js.min.js +0 -0
  94. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/swagger/index.html +0 -0
  95. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  96. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/query_builder/__init__.py +0 -0
  97. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue/__init__.py +0 -0
  98. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue/job.py +0 -0
  99. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue/kafka_backend.py +0 -0
  100. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue/lite_backend.py +0 -0
  101. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue/mongo_backend.py +0 -0
  102. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue/rabbitmq_backend.py +0 -0
  103. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue_backends/__init__.py +0 -0
  104. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue_backends/kafka_backend.py +0 -0
  105. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue_backends/mongo_backend.py +0 -0
  106. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  107. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/__init__.py +0 -0
  108. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/models/Attachment.py +0 -0
  109. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/models/Channel.py +0 -0
  110. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/models/ChannelMember.py +0 -0
  111. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/models/Message.py +0 -0
  112. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/models/Workspace.py +0 -0
  113. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/models/__init__.py +0 -0
  114. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/realtime/storage.py +0 -0
  115. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/__init__.py +0 -0
  116. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  117. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_badges.scss +0 -0
  118. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  119. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_cards.scss +0 -0
  120. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_forms.scss +0 -0
  121. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_grid.scss +0 -0
  122. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_modals.scss +0 -0
  123. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_nav.scss +0 -0
  124. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_pagination.scss +0 -0
  125. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_reset.scss +0 -0
  126. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_tables.scss +0 -0
  127. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_typography.scss +0 -0
  128. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  129. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/_variables.scss +0 -0
  130. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/base.scss +0 -0
  131. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/colors.scss +0 -0
  132. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/scss/tina4css/tina4.scss +0 -0
  133. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/seeder/__init__.py +0 -0
  134. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/service/__init__.py +0 -0
  135. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/session/__init__.py +0 -0
  136. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/session_handlers/__init__.py +0 -0
  137. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  138. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/session_handlers/redis_handler.py +0 -0
  139. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/session_handlers/valkey_handler.py +0 -0
  140. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/swagger/__init__.py +0 -0
  141. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/components/crud.twig +0 -0
  142. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  143. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  144. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/docker/python/Dockerfile +0 -0
  145. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  146. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/302.twig +0 -0
  147. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/401.twig +0 -0
  148. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/403.twig +0 -0
  149. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/404.twig +0 -0
  150. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/500.twig +0 -0
  151. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/502.twig +0 -0
  152. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/503.twig +0 -0
  153. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/errors/base.twig +0 -0
  154. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/frontend/README.md +0 -0
  155. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/templates/readme.md +0 -0
  156. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/test/__init__.py +0 -0
  157. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/test_client/__init__.py +0 -0
  158. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  159. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  160. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  161. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  162. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  163. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  164. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  165. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  166. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  167. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  168. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  169. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  170. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/validator/__init__.py +0 -0
  171. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/websocket/__init__.py +0 -0
  172. {tina4_python-3.13.91 → tina4_python-3.13.92}/tina4_python/websocket/backplane.py +0 -0
  173. {tina4_python-3.13.91 → tina4_python-3.13.92}/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.91
3
+ Version: 3.13.92
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.91"
3
+ version = "3.13.92"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -196,19 +196,51 @@ def _resolve_secret(secret: str = None) -> str:
196
196
  return env_secret
197
197
 
198
198
 
199
+ # Supported JWT algorithms. HMAC only — the whole family is in hashlib, so this
200
+ # stays zero-dependency. The header's "alg" is now always the one we actually sign
201
+ # with (python#105): the digest is looked up here rather than hardcoded.
202
+ _HMAC_ALGORITHMS = {
203
+ "HS256": hashlib.sha256,
204
+ "HS384": hashlib.sha384,
205
+ "HS512": hashlib.sha512,
206
+ }
207
+
208
+ # Seconds of clock skew tolerated on the "nbf" (not-before) claim. Without this a
209
+ # token minted on one host and validated on another a second behind is rejected
210
+ # for no real reason; RFC 7519 explicitly allows "a small leeway".
211
+ _JWT_LEEWAY_SECONDS = 60
212
+
213
+
214
+ def _resolve_algorithm(algorithm: str = None) -> str:
215
+ """Pick the JWT algorithm: explicit arg, else TINA4_JWT_ALGORITHM, else HS256.
216
+
217
+ Raises ValueError naming the supported set when asked for one we cannot sign
218
+ (python#106 — the env var was registered in the CLI's known_vars and then
219
+ silently ignored, so a user could set HS512 and still get HS256 tokens).
220
+ """
221
+ chosen = (algorithm or os.environ.get("TINA4_JWT_ALGORITHM") or "HS256").strip()
222
+ if chosen not in _HMAC_ALGORITHMS:
223
+ raise ValueError(
224
+ f"Unsupported JWT algorithm {chosen!r}. Tina4 signs with "
225
+ f"{', '.join(sorted(_HMAC_ALGORITHMS))} (HMAC only, zero-dependency). "
226
+ f"Set TINA4_JWT_ALGORITHM to one of those."
227
+ )
228
+ return chosen
229
+
230
+
199
231
  class Auth:
200
232
  """JWT authentication and password hashing — zero dependencies."""
201
233
 
202
- def __init__(self, secret: str = None, algorithm: str = "HS256",
234
+ def __init__(self, secret: str = None, algorithm: str = None,
203
235
  expires_in: int = None):
204
236
  """
205
237
  Args:
206
238
  secret: Signing secret (falls back to the TINA4_SECRET env var).
207
- algorithm: JWT algorithm (default HS256).
239
+ algorithm: JWT algorithm (falls back to TINA4_JWT_ALGORITHM, then HS256).
208
240
  expires_in: Token lifetime in seconds (default 3600).
209
241
  """
210
242
  self.secret = secret if secret is not None else _resolve_secret()
211
- self.algorithm = algorithm
243
+ self.algorithm = _resolve_algorithm(algorithm)
212
244
  # JWT expiry env var:
213
245
  # - TINA4_TOKEN_EXPIRES_IN (preferred — matches docs and the
214
246
  # form-token expiry env var used by Frond)
@@ -275,13 +307,29 @@ class Auth:
275
307
  return None
276
308
 
277
309
  h, p, sig = parts
310
+
311
+ # Pin the algorithm to OUR configured one instead of trusting the
312
+ # token's header. A token asking to be verified as anything else --
313
+ # "none", a weaker HMAC, or an RSA alg we do not implement -- is
314
+ # rejected before any signature work. Matches the Ruby check.
315
+ header = json.loads(_b64url_decode(h))
316
+ if header.get("alg") != self.algorithm:
317
+ return None
318
+
278
319
  expected = self._sign(f"{h}.{p}")
279
320
  if not hmac.compare_digest(sig, expected):
280
321
  return None
281
322
 
282
323
  payload = json.loads(_b64url_decode(p))
283
324
 
284
- if "exp" in payload and time.time() > payload["exp"]:
325
+ now = time.time()
326
+ if "exp" in payload and now > payload["exp"]:
327
+ return None
328
+
329
+ # "nbf" (not-before): a post-dated token is not valid yet. Was
330
+ # honoured only by Ruby, so Python/PHP/Node accepted tokens their
331
+ # issuer had explicitly marked as not-yet-usable (python#107).
332
+ if "nbf" in payload and now + _JWT_LEEWAY_SECONDS < payload["nbf"]:
285
333
  return None
286
334
 
287
335
  return payload
@@ -314,8 +362,10 @@ class Auth:
314
362
  return self.get_token(payload, expires_in=expires_in)
315
363
 
316
364
  def _sign(self, message: str) -> str:
365
+ # Digest comes from the configured algorithm, so the "alg" we advertise in
366
+ # the header is the one that actually produced this signature.
317
367
  sig = hmac.new(
318
- self.secret.encode(), message.encode(), hashlib.sha256
368
+ self.secret.encode(), message.encode(), _HMAC_ALGORITHMS[self.algorithm]
319
369
  ).digest()
320
370
  return _b64url_encode(sig)
321
371
 
@@ -424,16 +474,24 @@ class Auth:
424
474
  # ── Request Auth Helper ───────────────────────────────────────
425
475
 
426
476
  @_DualMethod
427
- def authenticate_request(self, headers: dict, secret: str = None, algorithm: str = "HS256") -> dict | None:
477
+ def authenticate_request(self, headers: dict, secret: str = None, algorithm: str = None) -> dict | None:
428
478
  """Extract and validate auth from request headers.
429
479
 
430
480
  Args:
431
481
  secret: Override signing secret (default: self.secret / TINA4_SECRET env var).
432
- algorithm: JWT algorithm override (default: "HS256").
482
+ algorithm: JWT algorithm override (default: self.algorithm / TINA4_JWT_ALGORITHM).
433
483
 
434
484
  Checks: Bearer JWT, Bearer API key, Basic auth.
435
485
  Returns payload dict on success, None on failure.
436
486
  """
487
+ # Both overrides used to be accepted and then dropped on the floor: the
488
+ # body called self.valid_token(), so a caller passing secret= or
489
+ # algorithm= silently got this instance's values instead. Honour them.
490
+ if secret is not None or algorithm is not None:
491
+ return Auth(secret=secret if secret is not None else self.secret,
492
+ algorithm=algorithm or self.algorithm,
493
+ expires_in=self.expires_in).authenticate_request(headers)
494
+
437
495
  auth_header = headers.get("authorization", "")
438
496
 
439
497
  if auth_header.startswith("Bearer "):
@@ -497,10 +555,10 @@ def refresh_token(token: str, expires_in: int = 60) -> str | None:
497
555
  return Auth.refresh_token_static(token, expires_in=expires_in)
498
556
 
499
557
 
500
- def authenticate_request(headers: dict, secret: str = None, algorithm: str = "HS256") -> dict | None:
558
+ def authenticate_request(headers: dict, secret: str = None, algorithm: str = None) -> dict | None:
501
559
  """Validate auth from request headers — reads TINA4_SECRET from env."""
502
- if secret is not None:
503
- return Auth(secret=secret).authenticate_request(headers, algorithm=algorithm)
560
+ if secret is not None or algorithm is not None:
561
+ return Auth(secret=secret, algorithm=algorithm).authenticate_request(headers)
504
562
  return Auth.authenticate_request_static(headers)
505
563
 
506
564
 
File without changes