regstack 0.3.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. {regstack-0.3.0 → regstack-0.5.0}/CLAUDE.md +31 -2
  2. {regstack-0.3.0 → regstack-0.5.0}/PKG-INFO +14 -7
  3. {regstack-0.3.0 → regstack-0.5.0}/README.md +9 -6
  4. {regstack-0.3.0 → regstack-0.5.0}/docs/api.md +73 -0
  5. {regstack-0.3.0 → regstack-0.5.0}/docs/architecture.md +48 -2
  6. {regstack-0.3.0 → regstack-0.5.0}/docs/changelog.md +45 -0
  7. regstack-0.5.0/docs/cli.md +187 -0
  8. {regstack-0.3.0 → regstack-0.5.0}/docs/conf.py +8 -1
  9. {regstack-0.3.0 → regstack-0.5.0}/docs/configuration.md +42 -8
  10. {regstack-0.3.0 → regstack-0.5.0}/docs/embedding.md +63 -8
  11. {regstack-0.3.0 → regstack-0.5.0}/docs/index.md +57 -2
  12. {regstack-0.3.0 → regstack-0.5.0}/docs/oauth.md +19 -0
  13. {regstack-0.3.0 → regstack-0.5.0}/docs/quickstart.md +1 -1
  14. regstack-0.5.0/docs/security-reports/README.md +15 -0
  15. {regstack-0.3.0 → regstack-0.5.0}/docs/security.md +71 -0
  16. {regstack-0.3.0 → regstack-0.5.0}/docs/theming.md +46 -1
  17. {regstack-0.3.0 → regstack-0.5.0}/pyproject.toml +15 -1
  18. regstack-0.5.0/scripts/security-review-prompt.md +583 -0
  19. regstack-0.5.0/src/regstack/cli/__main__.py +64 -0
  20. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/base.py +25 -27
  21. regstack-0.5.0/src/regstack/version.py +1 -0
  22. regstack-0.5.0/src/regstack/wizard/__init__.py +5 -0
  23. regstack-0.5.0/src/regstack/wizard/oauth_google/__init__.py +6 -0
  24. regstack-0.5.0/src/regstack/wizard/oauth_google/cli.py +224 -0
  25. regstack-0.5.0/src/regstack/wizard/oauth_google/routes.py +269 -0
  26. regstack-0.5.0/src/regstack/wizard/oauth_google/server.py +121 -0
  27. regstack-0.5.0/src/regstack/wizard/oauth_google/static/wizard.css +316 -0
  28. regstack-0.5.0/src/regstack/wizard/oauth_google/static/wizard.js +353 -0
  29. regstack-0.5.0/src/regstack/wizard/oauth_google/templates/wizard.html +260 -0
  30. regstack-0.5.0/src/regstack/wizard/oauth_google/validators.py +259 -0
  31. regstack-0.5.0/src/regstack/wizard/oauth_google/window.py +72 -0
  32. regstack-0.5.0/src/regstack/wizard/oauth_google/writer.py +248 -0
  33. regstack-0.5.0/src/regstack/wizard/theme_designer/__init__.py +8 -0
  34. regstack-0.5.0/src/regstack/wizard/theme_designer/cli.py +181 -0
  35. regstack-0.5.0/src/regstack/wizard/theme_designer/routes.py +278 -0
  36. regstack-0.5.0/src/regstack/wizard/theme_designer/server.py +87 -0
  37. regstack-0.5.0/src/regstack/wizard/theme_designer/static/designer.css +348 -0
  38. regstack-0.5.0/src/regstack/wizard/theme_designer/static/designer.js +272 -0
  39. regstack-0.5.0/src/regstack/wizard/theme_designer/templates/designer.html +89 -0
  40. regstack-0.5.0/src/regstack/wizard/theme_designer/validators.py +145 -0
  41. regstack-0.5.0/src/regstack/wizard/theme_designer/window.py +63 -0
  42. regstack-0.5.0/src/regstack/wizard/theme_designer/writer.py +196 -0
  43. {regstack-0.3.0 → regstack-0.5.0}/tasks.py +12 -1
  44. regstack-0.5.0/tests/e2e/conftest.py +121 -0
  45. regstack-0.5.0/tests/e2e/test_theme_designer.py +87 -0
  46. regstack-0.5.0/tests/e2e/test_wizard_oauth_flow.py +144 -0
  47. regstack-0.5.0/tests/unit/__init__.py +0 -0
  48. regstack-0.5.0/tests/unit/test_cli_migrate.py +151 -0
  49. regstack-0.5.0/tests/unit/test_theme_designer_cli.py +190 -0
  50. regstack-0.5.0/tests/unit/test_theme_designer_routes.py +179 -0
  51. regstack-0.5.0/tests/unit/test_theme_designer_validators.py +154 -0
  52. regstack-0.5.0/tests/unit/test_theme_designer_writer.py +156 -0
  53. regstack-0.5.0/tests/unit/test_wizard_oauth_cli.py +230 -0
  54. regstack-0.5.0/tests/unit/test_wizard_oauth_routes.py +232 -0
  55. regstack-0.5.0/tests/unit/test_wizard_oauth_validators.py +257 -0
  56. regstack-0.5.0/tests/unit/test_wizard_oauth_writer.py +293 -0
  57. {regstack-0.3.0 → regstack-0.5.0}/uv.lock +269 -1
  58. regstack-0.3.0/docs/cli.md +0 -87
  59. regstack-0.3.0/src/regstack/cli/__main__.py +0 -29
  60. regstack-0.3.0/src/regstack/version.py +0 -1
  61. {regstack-0.3.0 → regstack-0.5.0}/.github/workflows/publish.yml +0 -0
  62. {regstack-0.3.0 → regstack-0.5.0}/.github/workflows/test.yml +0 -0
  63. {regstack-0.3.0 → regstack-0.5.0}/.gitignore +0 -0
  64. {regstack-0.3.0 → regstack-0.5.0}/.python-version +0 -0
  65. {regstack-0.3.0 → regstack-0.5.0}/.readthedocs.yaml +0 -0
  66. {regstack-0.3.0 → regstack-0.5.0}/CHANGELOG.md +0 -0
  67. {regstack-0.3.0 → regstack-0.5.0}/LICENSE +0 -0
  68. {regstack-0.3.0 → regstack-0.5.0}/NOTICE +0 -0
  69. {regstack-0.3.0 → regstack-0.5.0}/SECURITY.md +0 -0
  70. {regstack-0.3.0 → regstack-0.5.0}/docs/_static/.gitkeep +0 -0
  71. {regstack-0.3.0 → regstack-0.5.0}/docs/_templates/.gitkeep +0 -0
  72. {regstack-0.3.0 → regstack-0.5.0}/examples/_common/__init__.py +0 -0
  73. {regstack-0.3.0 → regstack-0.5.0}/examples/_common/app.py +0 -0
  74. {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/README.md +0 -0
  75. {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/branding/theme.css +0 -0
  76. {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/main.py +0 -0
  77. {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/regstack.toml +0 -0
  78. {regstack-0.3.0 → regstack-0.5.0}/examples/postgres/README.md +0 -0
  79. {regstack-0.3.0 → regstack-0.5.0}/examples/postgres/main.py +0 -0
  80. {regstack-0.3.0 → regstack-0.5.0}/examples/postgres/regstack.toml +0 -0
  81. {regstack-0.3.0 → regstack-0.5.0}/examples/sqlite/README.md +0 -0
  82. {regstack-0.3.0 → regstack-0.5.0}/examples/sqlite/main.py +0 -0
  83. {regstack-0.3.0 → regstack-0.5.0}/examples/sqlite/regstack.toml +0 -0
  84. {regstack-0.3.0 → regstack-0.5.0}/regstack.toml.example +0 -0
  85. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/__init__.py +0 -0
  86. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/app.py +0 -0
  87. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/__init__.py +0 -0
  88. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/clock.py +0 -0
  89. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/dependencies.py +0 -0
  90. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/jwt.py +0 -0
  91. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/lockout.py +0 -0
  92. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/mfa.py +0 -0
  93. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/password.py +0 -0
  94. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/tokens.py +0 -0
  95. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/__init__.py +0 -0
  96. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/base.py +0 -0
  97. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/factory.py +0 -0
  98. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/__init__.py +0 -0
  99. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/backend.py +0 -0
  100. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/client.py +0 -0
  101. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/indexes.py +0 -0
  102. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
  103. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
  104. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
  105. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
  106. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/oauth_identity_repo.py +0 -0
  107. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/oauth_state_repo.py +0 -0
  108. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -0
  109. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
  110. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/protocols.py +0 -0
  111. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/__init__.py +0 -0
  112. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/backend.py +0 -0
  113. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/__init__.py +0 -0
  114. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/env.py +0 -0
  115. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
  116. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
  117. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/versions/0002_oauth.py +0 -0
  118. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/__init__.py +0 -0
  119. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
  120. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
  121. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
  122. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/oauth_identity_repo.py +0 -0
  123. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/oauth_state_repo.py +0 -0
  124. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
  125. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
  126. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/schema.py +0 -0
  127. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/types.py +0 -0
  128. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/__init__.py +0 -0
  129. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/_runtime.py +0 -0
  130. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/admin.py +0 -0
  131. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/doctor.py +0 -0
  132. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/init.py +0 -0
  133. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/migrate.py +0 -0
  134. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/__init__.py +0 -0
  135. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/loader.py +0 -0
  136. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/schema.py +0 -0
  137. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/secrets.py +0 -0
  138. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/__init__.py +0 -0
  139. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/base.py +0 -0
  140. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/composer.py +0 -0
  141. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/console.py +0 -0
  142. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/factory.py +0 -0
  143. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/ses.py +0 -0
  144. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/smtp.py +0 -0
  145. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/email_change.html +0 -0
  146. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/email_change.subject.txt +0 -0
  147. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/email_change.txt +0 -0
  148. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/password_reset.html +0 -0
  149. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/password_reset.subject.txt +0 -0
  150. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/password_reset.txt +0 -0
  151. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
  152. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
  153. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/verification.html +0 -0
  154. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/verification.subject.txt +0 -0
  155. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/verification.txt +0 -0
  156. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/hooks/__init__.py +0 -0
  157. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/hooks/events.py +0 -0
  158. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/__init__.py +0 -0
  159. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/_objectid.py +0 -0
  160. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/login_attempt.py +0 -0
  161. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/mfa_code.py +0 -0
  162. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/oauth_identity.py +0 -0
  163. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/oauth_state.py +0 -0
  164. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/pending_registration.py +0 -0
  165. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/user.py +0 -0
  166. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/__init__.py +0 -0
  167. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/errors.py +0 -0
  168. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/providers/__init__.py +0 -0
  169. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/providers/google.py +0 -0
  170. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/registry.py +0 -0
  171. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/__init__.py +0 -0
  172. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/_schemas.py +0 -0
  173. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/account.py +0 -0
  174. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/admin.py +0 -0
  175. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/login.py +0 -0
  176. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/logout.py +0 -0
  177. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/oauth.py +0 -0
  178. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/password.py +0 -0
  179. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/phone.py +0 -0
  180. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/register.py +0 -0
  181. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/verify.py +0 -0
  182. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/__init__.py +0 -0
  183. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/base.py +0 -0
  184. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/factory.py +0 -0
  185. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/null.py +0 -0
  186. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/sns.py +0 -0
  187. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/twilio.py +0 -0
  188. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/__init__.py +0 -0
  189. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/pages.py +0 -0
  190. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/static/css/core.css +0 -0
  191. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/static/css/theme.css +0 -0
  192. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/static/js/regstack.js +0 -0
  193. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
  194. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/forgot.html +0 -0
  195. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/login.html +0 -0
  196. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/me.html +0 -0
  197. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
  198. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/oauth_complete.html +0 -0
  199. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/register.html +0 -0
  200. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/reset.html +0 -0
  201. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/verify.html +0 -0
  202. {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/base.html +0 -0
  203. {regstack-0.3.0 → regstack-0.5.0}/tasks/oauth-design.md +0 -0
  204. {regstack-0.3.0 → regstack-0.5.0}/tests/__init__.py +0 -0
  205. {regstack-0.3.0 → regstack-0.5.0}/tests/_fake_google/__init__.py +0 -0
  206. {regstack-0.3.0 → regstack-0.5.0}/tests/_fake_google/provider.py +0 -0
  207. {regstack-0.3.0 → regstack-0.5.0}/tests/conftest.py +0 -0
  208. {regstack-0.3.0/tests/integration → regstack-0.5.0/tests/e2e}/__init__.py +0 -0
  209. {regstack-0.3.0/tests/unit → regstack-0.5.0/tests/integration}/__init__.py +0 -0
  210. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_account_management.py +0 -0
  211. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_admin_router.py +0 -0
  212. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_happy_path.py +0 -0
  213. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_indexes.py +0 -0
  214. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_login_lockout.py +0 -0
  215. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_mfa.py +0 -0
  216. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_oauth_google_router.py +0 -0
  217. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_oauth_repos.py +0 -0
  218. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_oauth_ui.py +0 -0
  219. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_password_reset.py +0 -0
  220. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_sql_migrations.py +0 -0
  221. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_ui_router.py +0 -0
  222. {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_verification.py +0 -0
  223. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_base_install_imports.py +0 -0
  224. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_cli.py +0 -0
  225. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_cli_doctor.py +0 -0
  226. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_cli_init.py +0 -0
  227. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_config_loader.py +0 -0
  228. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_jwt.py +0 -0
  229. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_lockout.py +0 -0
  230. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_mail_composer.py +0 -0
  231. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_mfa_code_repo.py +0 -0
  232. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_oauth_google.py +0 -0
  233. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_password.py +0 -0
  234. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_ses_backend.py +0 -0
  235. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_sms.py +0 -0
  236. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_smtp_backend.py +0 -0
  237. {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_ui_env.py +0 -0
@@ -60,8 +60,37 @@ The full plan, including milestone scope and deferred items, lives at
60
60
  Phone setup uses a separate signed `phone_setup` JWT carrying the
61
61
  proposed phone as a custom claim — same per-purpose key derivation as
62
62
  password-reset and email-change.
63
- - **OAuth**explicitly deferred. The `oauth/` package will hold a provider
64
- ABC only; concrete providers (Google first) come post-v1.
63
+ - **OAuth — done (0.3.0).** `OAuthProvider` ABC + `OAuthRegistry`,
64
+ Google provider (Authorization Code with PKCE, ID-token verification
65
+ via `pyjwt[crypto]` + `PyJWKClient`), 5 JSON endpoints,
66
+ `/account/oauth-complete` SSR token-handoff page, "Sign in with
67
+ Google" button on `/account/login`, Connected-accounts panel on
68
+ `/account/me`. Optional extra: `oauth = ["pyjwt[crypto]>=2.8"]`.
69
+ - **OAuth setup wizard — done.** `regstack oauth setup` opens a native
70
+ pywebview window over a 127.0.0.1 FastAPI server (random port +
71
+ one-shot launch token). 12-step SPA, per-step server validation,
72
+ non-clobbering tomlkit merge into `regstack.toml` +
73
+ `regstack.secrets.env`. Lives in `src/regstack/wizard/oauth_google/`;
74
+ Click subcommand registered through a `_LazyOauthGroup` so
75
+ `regstack init` / `doctor` don't pay the pywebview/uvicorn import
76
+ cost. `--print-only` mode runs the same merge headlessly. Tested at
77
+ four layers: validators (unit), writer (golden-file), routes
78
+ (TestClient), full SPA flow (Playwright e2e). Run e2e with
79
+ `inv test-e2e`; `inv test-all` chains it after the backend matrix.
80
+ - **Theme designer — done.** `regstack theme design` opens a sibling
81
+ pywebview window with controls for every `--rs-*` variable and a
82
+ real-time preview of the bundled SSR widgets (renders the same
83
+ `.rs-*` classes the SSR pages render, so what you see is what
84
+ you'll ship). Save writes `regstack-theme.css`; the designer
85
+ round-trips values back into the form on next launch so iteration
86
+ is non-destructive. Lives in `src/regstack/wizard/theme_designer/`,
87
+ registered via a `_LazyThemeGroup` mirroring the OAuth pattern.
88
+ Same four-layer test stack (validators / writer / routes /
89
+ Playwright e2e). The shared scaffold (FastAPI app, uvicorn launcher,
90
+ pywebview shim, e2e fixture pattern) is duplicated rather than
91
+ abstracted into a base class — the two tools have meaningfully
92
+ different inputs and an early shared base would calcify the wrong
93
+ decisions. Refactor only when a third pywebview tool lands.
65
94
 
66
95
  ## Three kinds of single-use proof
67
96
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: regstack
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Embeddable user registration, login, and account management for FastAPI apps. SQLite / Postgres / MongoDB.
5
5
  Project-URL: Homepage, https://github.com/jdrumgoole/regstack
6
6
  Project-URL: Repository, https://github.com/jdrumgoole/regstack
@@ -29,7 +29,10 @@ Requires-Dist: pydantic-settings>=2.2
29
29
  Requires-Dist: pydantic>=2.6
30
30
  Requires-Dist: pyjwt>=2.8
31
31
  Requires-Dist: python-multipart>=0.0.9
32
+ Requires-Dist: pywebview>=5.0
32
33
  Requires-Dist: sqlalchemy[asyncio]>=2.0
34
+ Requires-Dist: tomlkit>=0.13
35
+ Requires-Dist: uvicorn[standard]>=0.29
33
36
  Provides-Extra: dev
34
37
  Requires-Dist: anyio>=4.3; extra == 'dev'
35
38
  Requires-Dist: asyncpg>=0.29; extra == 'dev'
@@ -40,6 +43,7 @@ Requires-Dist: pyjwt[crypto]>=2.8; extra == 'dev'
40
43
  Requires-Dist: pymongo>=4.9; extra == 'dev'
41
44
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
42
45
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
46
+ Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
43
47
  Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
44
48
  Requires-Dist: pytest>=8.0; extra == 'dev'
45
49
  Requires-Dist: ruff>=0.4; extra == 'dev'
@@ -78,9 +82,9 @@ auth bugs.**
78
82
 
79
83
  `pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
80
84
  or [MongoDB](https://www.mongodb.com/), and you have register / login /
81
- verify-email / reset-password / change-email / delete-account / optional SMS
82
- two-factor / admin endpoints / themable HTML pages — all behind a small Python
83
- API and one config file.
85
+ verify-email / reset-password / change-email / delete-account / Sign in
86
+ with Google / optional SMS two-factor / admin endpoints / themable
87
+ HTML pages — all behind a small Python API and one config file.
84
88
 
85
89
  📚 **Docs:** <https://regstack.readthedocs.io>
86
90
  &nbsp;·&nbsp;
@@ -132,13 +136,14 @@ result everywhere is what regstack is for.
132
136
  ✔ Forgot / reset password — anti-enumeration: identical responses
133
137
  ✔ Change password (revokes old tokens) / change email (re-verify)
134
138
  ✔ Delete account
139
+ ✔ Sign in with Google (PKCE + ID-token verification, opt-in)
135
140
  ✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
136
141
  ✔ Server-side login lockout (HTTP 429 + Retry-After)
137
142
  ✔ Admin endpoints (list / disable / delete users, stats)
138
143
  ✔ Server-rendered HTML pages, theme with one CSS file
139
144
  ✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
140
145
  ✔ Argon2 password hashing, CSP-friendly templates
141
- ✔ Setup wizard (`regstack init`) and config validator (`regstack doctor`)
146
+ ✔ Setup wizards (live in their own pywebview windows): `regstack init` (project bootstrap), `regstack oauth setup` (guided Google OAuth client config), `regstack theme design` (live theme designer with preview), and `regstack doctor` (config validator)
142
147
  ✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
143
148
  ```
144
149
 
@@ -181,7 +186,7 @@ register from the command line:
181
186
  ```bash
182
187
  curl -X POST http://localhost:8000/api/auth/register \
183
188
  -H 'content-type: application/json' \
184
- -d '{"email":"alice@example.com","password":"hunter2hunter2","full_name":"Alice"}'
189
+ -d '{"email":"alice@app.example.com","password":"<password>","full_name":"Alice"}'
185
190
  ```
186
191
 
187
192
  The bundled example serves themed SSR pages at `/account/*`, prints
@@ -245,7 +250,9 @@ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdr
245
250
 
246
251
  Alpha. Single-file SQLite is the default and runs with no infrastructure;
247
252
  PostgreSQL and MongoDB backends pass the same parametrized integration
248
- suite. Latest tagged release: `v0.2.1`. See the
253
+ suite. OAuth (Google) shipped in `v0.3.0`; the `regstack oauth setup`
254
+ guided wizard in `v0.4.0`; the live `regstack theme design` tool in
255
+ `v0.5.0`. Latest tagged release: `v0.5.0`. See the
249
256
  [changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
250
257
  for the per-release breakdown.
251
258
 
@@ -11,9 +11,9 @@ auth bugs.**
11
11
 
12
12
  `pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
13
13
  or [MongoDB](https://www.mongodb.com/), and you have register / login /
14
- verify-email / reset-password / change-email / delete-account / optional SMS
15
- two-factor / admin endpoints / themable HTML pages — all behind a small Python
16
- API and one config file.
14
+ verify-email / reset-password / change-email / delete-account / Sign in
15
+ with Google / optional SMS two-factor / admin endpoints / themable
16
+ HTML pages — all behind a small Python API and one config file.
17
17
 
18
18
  📚 **Docs:** <https://regstack.readthedocs.io>
19
19
  &nbsp;·&nbsp;
@@ -65,13 +65,14 @@ result everywhere is what regstack is for.
65
65
  ✔ Forgot / reset password — anti-enumeration: identical responses
66
66
  ✔ Change password (revokes old tokens) / change email (re-verify)
67
67
  ✔ Delete account
68
+ ✔ Sign in with Google (PKCE + ID-token verification, opt-in)
68
69
  ✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
69
70
  ✔ Server-side login lockout (HTTP 429 + Retry-After)
70
71
  ✔ Admin endpoints (list / disable / delete users, stats)
71
72
  ✔ Server-rendered HTML pages, theme with one CSS file
72
73
  ✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
73
74
  ✔ Argon2 password hashing, CSP-friendly templates
74
- ✔ Setup wizard (`regstack init`) and config validator (`regstack doctor`)
75
+ ✔ Setup wizards (live in their own pywebview windows): `regstack init` (project bootstrap), `regstack oauth setup` (guided Google OAuth client config), `regstack theme design` (live theme designer with preview), and `regstack doctor` (config validator)
75
76
  ✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
76
77
  ```
77
78
 
@@ -114,7 +115,7 @@ register from the command line:
114
115
  ```bash
115
116
  curl -X POST http://localhost:8000/api/auth/register \
116
117
  -H 'content-type: application/json' \
117
- -d '{"email":"alice@example.com","password":"hunter2hunter2","full_name":"Alice"}'
118
+ -d '{"email":"alice@app.example.com","password":"<password>","full_name":"Alice"}'
118
119
  ```
119
120
 
120
121
  The bundled example serves themed SSR pages at `/account/*`, prints
@@ -178,7 +179,9 @@ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdr
178
179
 
179
180
  Alpha. Single-file SQLite is the default and runs with no infrastructure;
180
181
  PostgreSQL and MongoDB backends pass the same parametrized integration
181
- suite. Latest tagged release: `v0.2.1`. See the
182
+ suite. OAuth (Google) shipped in `v0.3.0`; the `regstack oauth setup`
183
+ guided wizard in `v0.4.0`; the live `regstack theme design` tool in
184
+ `v0.5.0`. Latest tagged release: `v0.5.0`. See the
182
185
  [changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
183
186
  for the per-release breakdown.
184
187
 
@@ -18,6 +18,7 @@ The handful of things you import from `regstack` directly:
18
18
  - [`RegStackConfig`](#regstack.config.schema.RegStackConfig) — top-level config.
19
19
  - [`EmailConfig`](#regstack.config.schema.EmailConfig) — email-backend sub-config.
20
20
  - [`SmsConfig`](#regstack.config.schema.SmsConfig) — SMS-backend sub-config.
21
+ - [`OAuthConfig`](#regstack.config.schema.OAuthConfig) — OAuth provider sub-config.
21
22
 
22
23
  Most embeddings need only `RegStack` and `RegStackConfig`.
23
24
 
@@ -59,6 +60,11 @@ default.
59
60
  :show-inheritance:
60
61
  :exclude-members: model_config, model_fields, model_computed_fields
61
62
 
63
+ .. autoclass:: regstack.config.schema.OAuthConfig
64
+ :members:
65
+ :show-inheritance:
66
+ :exclude-members: model_config, model_fields, model_computed_fields
67
+
62
68
  .. autofunction:: regstack.config.loader.load_config
63
69
  ```
64
70
 
@@ -247,6 +253,73 @@ MessageBird, …) and pass the instance to `regstack.set_email_backend`
247
253
  .. autofunction:: regstack.sms.factory.build_sms_service
248
254
  ```
249
255
 
256
+ ## OAuth
257
+
258
+ Opt-in subsystem behind `enable_oauth` and the `oauth` extra. v1
259
+ ships Google; the abstraction is shaped so adding GitHub /
260
+ Microsoft / Apple later is a new module under
261
+ `regstack.oauth.providers` plus a registry entry. The full host
262
+ guide is in [OAuth](oauth.md); the threat model is in
263
+ [Security model](security.md#oauth-sign-in-with-google).
264
+
265
+ ### Provider abstraction
266
+
267
+ ```{eval-rst}
268
+ .. autoclass:: regstack.oauth.base.OAuthProvider
269
+ :members:
270
+
271
+ .. autoclass:: regstack.oauth.base.OAuthTokens
272
+ :members:
273
+
274
+ .. autoclass:: regstack.oauth.base.OAuthUserInfo
275
+ :members:
276
+
277
+ .. autoclass:: regstack.oauth.registry.OAuthRegistry
278
+ :members:
279
+
280
+ .. autoexception:: regstack.oauth.errors.OAuthError
281
+
282
+ .. autoexception:: regstack.oauth.errors.OAuthConfigError
283
+
284
+ .. autoexception:: regstack.oauth.errors.OAuthTokenExchangeError
285
+
286
+ .. autoexception:: regstack.oauth.errors.OAuthIdTokenError
287
+ ```
288
+
289
+ ### Google provider
290
+
291
+ ```{eval-rst}
292
+ .. autoclass:: regstack.oauth.providers.google.GoogleProvider
293
+ :members:
294
+ :show-inheritance:
295
+ ```
296
+
297
+ ### Identity + state storage
298
+
299
+ ```{eval-rst}
300
+ .. autoclass:: regstack.models.oauth_identity.OAuthIdentity
301
+ :members:
302
+ :exclude-members: model_config, model_fields, model_computed_fields
303
+
304
+ .. autoclass:: regstack.models.oauth_state.OAuthState
305
+ :members:
306
+ :exclude-members: model_config, model_fields, model_computed_fields
307
+
308
+ .. autoclass:: regstack.backends.protocols.OAuthIdentityRepoProtocol
309
+ :members:
310
+
311
+ .. autoclass:: regstack.backends.protocols.OAuthStateRepoProtocol
312
+ :members:
313
+
314
+ .. autoexception:: regstack.backends.protocols.OAuthIdentityAlreadyLinkedError
315
+ ```
316
+
317
+ ### Router
318
+
319
+ ```{eval-rst}
320
+ .. autofunction:: regstack.routers.oauth.build_oauth_router
321
+ ```
322
+
250
323
  ## Hooks
251
324
 
252
325
  The event bus regstack uses to fire side-effect notifications
@@ -139,8 +139,52 @@ The composite `router` conditionally includes:
139
139
  - `password` (forgot/reset) — when `enable_password_reset`.
140
140
  - `phone` and the `mfa-confirm` route — when `enable_sms_2fa`.
141
141
  - `admin` — when `enable_admin_router`.
142
-
143
- `ui_router` mounts the same conditional pages.
142
+ - `oauth` — when `enable_oauth` AND at least one provider is
143
+ registered on `rs.oauth`.
144
+
145
+ `ui_router` mounts the same conditional pages, plus
146
+ `/account/oauth-complete` when `enable_oauth` is on.
147
+
148
+ ## OAuth subsystem
149
+
150
+ Opt-in. Lives in `regstack.oauth/`; hosts pull it in via the
151
+ `oauth` extra (`pyjwt[crypto]>=2.8`). Imports are lazy — the
152
+ package keeps importing on a base install with no `cryptography`
153
+ installed, and the OAuth-specific modules only get loaded when
154
+ `enable_oauth` is on.
155
+
156
+ The shape is:
157
+
158
+ - `OAuthProvider` ABC — three methods: `authorization_url`,
159
+ `exchange_code`, `verify_id_token`.
160
+ - `OAuthRegistry` — name-keyed map of providers, scoped to one
161
+ `RegStack` instance. The `RegStack` constructor reads
162
+ `config.oauth` and registers `GoogleProvider` automatically when
163
+ `enable_oauth` and the credentials are set; hosts can also
164
+ register custom providers post-construction.
165
+ - `GoogleProvider` — Authorization Code with PKCE, ID-token
166
+ verification via `pyjwt[crypto]` + `PyJWKClient` against Google's
167
+ JWKS. ~150 lines hand-rolled rather than pulling `authlib`.
168
+ - Two new repos via the protocol pattern:
169
+ `OAuthIdentityRepoProtocol` (links between regstack users and
170
+ external accounts; double-unique on `(provider, subject_id)` and
171
+ `(user_id, provider)`) and `OAuthStateRepoProtocol` (in-flight
172
+ state rows carrying the PKCE `code_verifier`, redirect target,
173
+ mode, and the `result_token` slot the SPA exchanges).
174
+ - `build_oauth_router(rs)` — the router with the five endpoints
175
+ (`/start`, `/callback`, `/exchange`, `/link/start`, `/link`) plus
176
+ `/oauth/providers` for the SSR connected-accounts panel.
177
+
178
+ The token-handoff round-trip avoids putting access tokens in URLs:
179
+ the callback stashes the freshly-minted session JWT on the state
180
+ row's `result_token`, redirects to `/account/oauth-complete?id=…`,
181
+ and the SPA POSTs that id back to `/oauth/exchange` to retrieve the
182
+ token. The exchange consumes the row atomically — the same id can't
183
+ be exchanged twice.
184
+
185
+ The full design (including the four-milestone build sequence and
186
+ the threat model) is in
187
+ [`tasks/oauth-design.md`](https://github.com/jdrumgoole/regstack/blob/main/tasks/oauth-design.md).
144
188
 
145
189
  ## Hooks
146
190
 
@@ -157,6 +201,8 @@ primary auth flow. Known events:
157
201
  - `phone_setup_started` / `mfa_login_started`
158
202
  - `mfa_enabled` / `mfa_disabled`
159
203
  - `user_deleted`
204
+ - `oauth_signin_started` / `oauth_signin_completed`
205
+ - `oauth_account_linked` / `oauth_account_unlinked`
160
206
 
161
207
  Hosts are free to subscribe to custom event names too — the registry
162
208
  is just a `defaultdict(list)`. Use this surface to push events into
@@ -3,6 +3,51 @@
3
3
  All notable changes to this project are documented here. Versions follow
4
4
  [Semantic Versioning](https://semver.org/) once `1.0.0` ships.
5
5
 
6
+ ## 0.5.0 — 2026-05-02
7
+
8
+ ### Added
9
+
10
+ - **Theme designer.** `regstack theme design` opens a native pywebview
11
+ window with controls for every `--rs-*` CSS custom property and a
12
+ real-time preview of the bundled SSR widgets (sign-in form, success
13
+ / error banners, danger-zone button). Saving writes
14
+ `regstack-theme.css`; the designer round-trips values back into the
15
+ form on next launch so iteration is non-destructive. `--print-only`
16
+ mode takes repeatable `--var NAME=VALUE` pairs (with a `dark:`
17
+ prefix for dark-scheme overrides) and writes the file headlessly.
18
+ Lives in `regstack.wizard.theme_designer`; registered as a lazy
19
+ Click subgroup so `regstack init` / `doctor` don't pay the
20
+ pywebview/uvicorn import cost.
21
+ - "Why use regstack" pitch in `docs/index.md` updated to surface the
22
+ two pywebview tools (`oauth setup` + `theme design`) as a
23
+ distinguishing feature vs. fastapi-users / Auth0 / Keycloak.
24
+
25
+ ### Docs
26
+
27
+ - New "About the examples" convention block at the top of
28
+ `docs/index.md`. Every URL, email, smtp host, and admin command
29
+ across the docs now extrapolates from the same fictional app at
30
+ `app.example.com` with `<username>` / `<password>` placeholders —
31
+ no more `user:pw@host/dbname` / `db.internal/myapp` mishmash.
32
+
33
+ ## 0.4.0 — 2026-05-02
34
+
35
+ ### Added
36
+
37
+ - **OAuth setup wizard.** `regstack oauth setup` opens a native
38
+ webview window that walks an operator through registering a Google
39
+ OAuth 2.0 client and merges the credentials into `regstack.toml` +
40
+ `regstack.secrets.env` non-destructively (preserves comments, other
41
+ tables, unrelated keys). 12-step SPA inside a local-only
42
+ 127.0.0.1 FastAPI server, gated by a per-launch random token. Each
43
+ Next click hits a server-side validator so the Write step can never
44
+ be reached with bad data. `--print-only` mode skips the GUI for
45
+ headless / CI use.
46
+ - Three new base dependencies: `pywebview>=5.0`, `tomlkit>=0.13`,
47
+ `uvicorn[standard]>=0.29` (the wizard's local server).
48
+ - `pytest-playwright` added to the `dev` extra; new `inv test-e2e`
49
+ task chained into `inv test-all`.
50
+
6
51
  ## 0.3.0 — 2026-04-30
7
52
 
8
53
  **OAuth — Sign in with Google.** Built across four PRs (M1–M4 of
@@ -0,0 +1,187 @@
1
+ # CLI reference
2
+
3
+ `regstack` is the entry point installed by the package. All sub-commands
4
+ share a config-loading model: programmatic kwargs > env vars >
5
+ `regstack.secrets.env` > `regstack.toml` > defaults. The `--config <path>`
6
+ flag overrides where the TOML file is found.
7
+
8
+ ## `regstack init`
9
+
10
+ Interactive wizard that writes `regstack.toml` and `regstack.secrets.env`
11
+ in the current directory. Asks which backend to use (SQLite default →
12
+ Postgres → MongoDB), generates a 64-byte JWT secret, runs DNS sanity
13
+ checks if asked, never provisions infrastructure.
14
+
15
+ ```bash
16
+ uv run regstack init
17
+ uv run regstack init --target /etc/app --force
18
+ ```
19
+
20
+ Options:
21
+
22
+ - `--target DIR` — directory to write the config files (default cwd).
23
+ - `--force` — overwrite without confirming.
24
+
25
+ Re-running the wizard prompts before overwriting unless `--force` is
26
+ passed; pre-existing answers aren't kept (the wizard is intentionally
27
+ stateless).
28
+
29
+ ## `regstack oauth setup`
30
+
31
+ Opens a guided 12-step wizard in a native webview window that walks you
32
+ through registering a Google OAuth 2.0 client (project selection,
33
+ consent screen, redirect URI, credentials) and merges the result into
34
+ your existing `regstack.toml` and `regstack.secrets.env`. The merge is
35
+ **non-clobbering** — comments, unrelated tables (`[email]`, `[sms]`,
36
+ …), and unrelated top-level keys are preserved. Re-run any time to
37
+ rotate credentials or change the linking policy.
38
+
39
+ ```bash
40
+ uv run regstack oauth setup
41
+ uv run regstack oauth setup --target /etc/app
42
+ ```
43
+
44
+ Options:
45
+
46
+ - `--target DIR` — directory containing (or to receive) `regstack.toml`
47
+ (default cwd).
48
+ - `--api-prefix PREFIX` — router prefix the host mounts regstack under
49
+ (default `/api/auth`). Used to compute the suggested redirect URI.
50
+ - `--port N` — pin the wizard server's TCP port (default: random free
51
+ port on `127.0.0.1`).
52
+ - `--print-only` — skip the GUI; print the TOML + secrets diff that
53
+ *would* be written to stdout, then exit. Useful for headless hosts
54
+ (CI, servers without a webview backend) and dry-run smoke tests.
55
+ Pair with `--client-id`, `--client-secret`, `--base-url`,
56
+ `--auto-link/--no-auto-link`, `--mfa/--no-mfa`.
57
+
58
+ The interactive mode requires a desktop environment with a webview
59
+ backend (WebKit on macOS, GTK / QtWebEngine on Linux, Edge WebView2 on
60
+ Windows). On a headless host it exits with a clear error pointing at
61
+ `--print-only`.
62
+
63
+ The wizard binds to `127.0.0.1` only and authenticates every API call
64
+ with a per-launch random token, so a hostile process on the same host
65
+ can't drive the write endpoint.
66
+
67
+ ## `regstack theme design`
68
+
69
+ Opens a live designer for `regstack-theme.css` in a native pywebview
70
+ window. The left pane has controls for every `--rs-*` CSS custom
71
+ property; the right pane renders the bundled SSR widgets (sign-in
72
+ form, success / error messages, danger-zone button) with your changes
73
+ applied in real time. Click **Save** to write the file; **Reset to
74
+ defaults** to start over; **Copy CSS** to put the generated
75
+ stylesheet on the clipboard without writing.
76
+
77
+ ```bash
78
+ uv run regstack theme design
79
+ uv run regstack theme design --target /var/www/static
80
+ ```
81
+
82
+ Options:
83
+
84
+ - `--target DIR` — directory to write `regstack-theme.css` into
85
+ (default cwd).
86
+ - `--filename NAME` — output filename
87
+ (default `regstack-theme.css`).
88
+ - `--port N` — pin the designer's TCP port (default: random free
89
+ port on `127.0.0.1`).
90
+ - `--print-only` — skip the GUI; write the file from `--var` pairs
91
+ and emit a JSON summary. For headless / CI use.
92
+ - `--var NAME=VALUE` — repeatable. Used with `--print-only`. Prefix
93
+ with `dark:` to set the dark-scheme value, e.g.
94
+ `--var dark:--rs-accent=#2dd4bf`.
95
+
96
+ Re-running the designer reloads the previous values out of the file,
97
+ so iterating on a theme is non-destructive. Only the `:root` and
98
+ `@media (prefers-color-scheme: dark)` blocks are managed by the
99
+ designer — anything else in the file is left alone.
100
+
101
+ Same security shape as `regstack oauth setup`: binds `127.0.0.1`
102
+ only, every API call requires a per-launch random token.
103
+
104
+ ## `regstack create-admin`
105
+
106
+ Create or promote a superuser. Idempotent.
107
+
108
+ ```bash
109
+ uv run regstack create-admin --email admin@app.example.com
110
+ uv run regstack create-admin --email admin@app.example.com --password 'long-strong-password'
111
+ uv run regstack create-admin --email admin@app.example.com --config /etc/app/regstack.toml
112
+ ```
113
+
114
+ Options:
115
+
116
+ - `--email EMAIL` *(required)*.
117
+ - `--password PW` — if omitted, prompts (with confirmation).
118
+ - `--config PATH` — TOML file to load (default: env or cwd).
119
+
120
+ If the user already exists, the command sets `is_superuser=True` and
121
+ keeps the existing password. If they don't exist, it creates the user
122
+ with `is_active=True`, `is_verified=True`, `is_superuser=True` and the
123
+ provided password.
124
+
125
+ ## `regstack migrate`
126
+
127
+ Runs the bundled Alembic migrations against the configured
128
+ `database_url`. Idempotent — re-running on a DB already at the target
129
+ revision is a no-op. Use this on SQL backends (SQLite / PostgreSQL)
130
+ to roll the schema forward to a new regstack release.
131
+
132
+ ```bash
133
+ uv run regstack migrate
134
+ uv run regstack migrate --target head
135
+ uv run regstack migrate --config /etc/app/regstack.toml --target 0001
136
+ ```
137
+
138
+ Options:
139
+
140
+ - `--config PATH` — TOML file to load (default: cwd / `$REGSTACK_CONFIG`).
141
+ - `--target REV` — revision to upgrade to (default `head`). Accepts
142
+ any Alembic revision spec: a revision id (`0001`), a relative step
143
+ (`+1`), or `head`.
144
+
145
+ Mongo backends are silently skipped: TTL indexes are installed by
146
+ `RegStack.install_schema()` on every app start, so there's no separate
147
+ migration story to run. Output prints the before / after revision and
148
+ exits non-zero if Alembic raises.
149
+
150
+ ## `regstack doctor`
151
+
152
+ Runs read-only validation against the loaded config and reports each
153
+ check on a green/red line. Exit code is the number of failed checks —
154
+ suitable for use in container health checks.
155
+
156
+ ```bash
157
+ uv run regstack doctor
158
+ uv run regstack doctor --config /etc/app/regstack.toml
159
+ uv run regstack doctor --check-dns
160
+ uv run regstack doctor --send-test-email alice@app.example.com
161
+ ```
162
+
163
+ Options:
164
+
165
+ - `--config PATH` — TOML file to load.
166
+ - `--check-dns` — run SPF / DMARC / MX `dig`s on the sender domain.
167
+ Internet-dependent; off by default.
168
+ - `--send-test-email TO` — actually send a probe email through the
169
+ configured backend. Costs real money on SES; off by default.
170
+
171
+ Default checks:
172
+
173
+ | Check | Pass criterion |
174
+ |-------|----------------|
175
+ | `jwt secret` | Present, ≥ 32 chars |
176
+ | `backend` | `Backend.ping()` succeeds (works for any backend) |
177
+ | `schema` | Mongo: required indexes present. SQL: `users` table responds to `count(*)` |
178
+ | `email backend` | `build_email_service(config.email)` instantiates |
179
+
180
+ Optional checks:
181
+
182
+ | Check | Pass criterion |
183
+ |-------|----------------|
184
+ | `dns mx` | At least one MX record on the sender domain |
185
+ | `dns spf` | A TXT record containing `v=spf1` |
186
+ | `dns dmarc` | A TXT record at `_dmarc.<domain>` containing `v=DMARC1` |
187
+ | `email send` | The configured backend's `send()` returned without raising |
@@ -38,7 +38,14 @@ extensions = [
38
38
  source_suffix = {".md": "markdown", ".rst": "restructuredtext"}
39
39
  master_doc = "index"
40
40
  templates_path = ["_templates"]
41
- exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
41
+ exclude_patterns = [
42
+ "_build",
43
+ "Thumbs.db",
44
+ ".DS_Store",
45
+ # Daily security review reports — these are GitHub-rendered
46
+ # markdown, not part of the published Sphinx site.
47
+ "security-reports/**",
48
+ ]
42
49
 
43
50
  # Suppress noisy autodoc warnings on dynamically-typed pydantic helpers.
44
51
  suppress_warnings = ["autodoc.import_object"]
@@ -63,6 +63,12 @@ addressed in env using a `__` separator: `REGSTACK_EMAIL__FROM_ADDRESS`.
63
63
  * - `mfa_code_collection`
64
64
  - `"mfa_codes"`
65
65
  -
66
+ * - `oauth_identity_collection`
67
+ - `"oauth_identities"`
68
+ -
69
+ * - `oauth_state_collection`
70
+ - `"oauth_states"`
71
+ -
66
72
  ```
67
73
 
68
74
  ## Backends
@@ -79,15 +85,16 @@ regstack picks a backend at construction time from the URL scheme of
79
85
  - Notes
80
86
 
81
87
  * - SQLite
82
- - `sqlite+aiosqlite:///./path.db`
88
+ - `sqlite+aiosqlite:///./dbname.db`
83
89
  - Default. Bundled in the base install — no extras needed.
84
90
  `:memory:` works too (per-test).
85
91
  * - Postgres
86
- - `postgresql+asyncpg://user:pw@host/dbname`
92
+ - `postgresql+asyncpg://<username>:<password>@dbhost.example.com:5432/dbname`
87
93
  - Requires the `postgres` extra (pulls in `asyncpg`). The driver is
88
94
  pinned to `+asyncpg` — sync drivers won't work.
89
95
  * - MongoDB
90
- - `mongodb://host:port/dbname` (or `mongodb+srv://`)
96
+ - `mongodb://<username>:<password>@dbhost.example.com:27017/dbname`
97
+ (or `mongodb+srv://<username>:<password>@app.abc123.mongodb.net/dbname`)
91
98
  - Requires the `mongo` extra (pulls in `pymongo`). Database is taken
92
99
  from the URL path; falls back to ``mongodb_database`` if absent.
93
100
  ```
@@ -165,7 +172,9 @@ The active backend exposes the same five repository protocols on
165
172
  - Mounts `/phone/*` routes and gates the MFA second step in `/login`.
166
173
  * - `enable_oauth`
167
174
  - `false`
168
- - Reserved. No providers ship in v1.
175
+ - Mounts `/oauth/*` routes when at least one provider is registered
176
+ (currently Google). Requires the ``oauth`` extra
177
+ (``pip install 'regstack[oauth]'``).
169
178
  ```
170
179
 
171
180
  ## Lockout (login)
@@ -226,14 +235,14 @@ The active backend exposes the same five repository protocols on
226
235
  ```toml
227
236
  [email]
228
237
  backend = "console" # console | smtp | ses
229
- from_address = "noreply@…"
230
- from_name = "MyApp"
238
+ from_address = "noreply@app.example.com"
239
+ from_name = "Example App"
231
240
 
232
241
  # smtp
233
- smtp_host = "smtp.example.com"
242
+ smtp_host = "smtp.app.example.com"
234
243
  smtp_port = 587
235
244
  smtp_starttls = true
236
- smtp_username = "myapp"
245
+ smtp_username = "<username>"
237
246
  # smtp_password is a SecretStr — set via REGSTACK_EMAIL__SMTP_PASSWORD
238
247
 
239
248
  # ses
@@ -256,6 +265,31 @@ twilio_account_sid = "AC…"
256
265
  # twilio_auth_token via REGSTACK_SMS__TWILIO_AUTH_TOKEN
257
266
  ```
258
267
 
268
+ `[oauth]` (`OAuthConfig`):
269
+
270
+ ```toml
271
+ [oauth]
272
+ google_client_id = "12345.apps.googleusercontent.com"
273
+ # google_client_secret via REGSTACK_OAUTH__GOOGLE_CLIENT_SECRET
274
+ # google_redirect_uri = "https://your.app/api/auth/oauth/google/callback"
275
+ # (default: f"{base_url}{api_prefix}/oauth/google/callback")
276
+
277
+ # Account-linking policy. Off by default — see docs/oauth.md and
278
+ # docs/security.md for the threat model. On = a Google sign-in for an
279
+ # existing email-registered user is auto-linked when Google's
280
+ # email_verified=true. Hosts choosing on are accepting the email-
281
+ # recycling-at-the-provider risk in exchange for less friction.
282
+ auto_link_verified_emails = false
283
+
284
+ # When true, an OAuth sign-in for a user with SMS MFA enabled still
285
+ # goes through the second-factor step. Off by default — the OAuth
286
+ # provider already authenticated the human.
287
+ enforce_mfa_on_oauth_signin = false
288
+
289
+ state_ttl_seconds = 300 # in-flight state row lifetime
290
+ completion_ttl_seconds = 30 # /oauth/exchange window after callback
291
+ ```
292
+
259
293
  ## SSR / theming
260
294
 
261
295
  ```{list-table}