regstack 0.2.1__tar.gz → 0.2.3__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 (184) hide show
  1. {regstack-0.2.1 → regstack-0.2.3}/CHANGELOG.md +20 -0
  2. {regstack-0.2.1 → regstack-0.2.3}/PKG-INFO +9 -11
  3. {regstack-0.2.1 → regstack-0.2.3}/README.md +8 -10
  4. regstack-0.2.3/docs/api.md +278 -0
  5. {regstack-0.2.1 → regstack-0.2.3}/docs/architecture.md +35 -44
  6. {regstack-0.2.1 → regstack-0.2.3}/docs/changelog.md +53 -0
  7. {regstack-0.2.1 → regstack-0.2.3}/docs/embedding.md +10 -13
  8. regstack-0.2.3/docs/index.md +141 -0
  9. {regstack-0.2.1 → regstack-0.2.3}/docs/quickstart.md +11 -11
  10. {regstack-0.2.1 → regstack-0.2.3}/docs/security.md +49 -68
  11. {regstack-0.2.1 → regstack-0.2.3}/pyproject.toml +1 -1
  12. regstack-0.2.3/src/regstack/app.py +330 -0
  13. regstack-0.2.3/src/regstack/auth/clock.py +73 -0
  14. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/dependencies.py +50 -9
  15. regstack-0.2.3/src/regstack/auth/jwt.py +256 -0
  16. regstack-0.2.3/src/regstack/auth/lockout.py +119 -0
  17. regstack-0.2.3/src/regstack/auth/password.py +65 -0
  18. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/protocols.py +31 -6
  19. regstack-0.2.3/src/regstack/email/base.py +71 -0
  20. regstack-0.2.3/src/regstack/hooks/events.py +98 -0
  21. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/__init__.py +21 -1
  22. regstack-0.2.3/src/regstack/sms/base.py +67 -0
  23. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/pages.py +49 -8
  24. regstack-0.2.3/src/regstack/version.py +1 -0
  25. {regstack-0.2.1 → regstack-0.2.3}/uv.lock +1 -1
  26. regstack-0.2.1/docs/api.md +0 -184
  27. regstack-0.2.1/docs/index.md +0 -99
  28. regstack-0.2.1/src/regstack/app.py +0 -166
  29. regstack-0.2.1/src/regstack/auth/clock.py +0 -29
  30. regstack-0.2.1/src/regstack/auth/jwt.py +0 -145
  31. regstack-0.2.1/src/regstack/auth/lockout.py +0 -59
  32. regstack-0.2.1/src/regstack/auth/password.py +0 -20
  33. regstack-0.2.1/src/regstack/email/base.py +0 -23
  34. regstack-0.2.1/src/regstack/hooks/events.py +0 -59
  35. regstack-0.2.1/src/regstack/sms/base.py +0 -24
  36. regstack-0.2.1/src/regstack/version.py +0 -1
  37. {regstack-0.2.1 → regstack-0.2.3}/.github/workflows/publish.yml +0 -0
  38. {regstack-0.2.1 → regstack-0.2.3}/.github/workflows/test.yml +0 -0
  39. {regstack-0.2.1 → regstack-0.2.3}/.gitignore +0 -0
  40. {regstack-0.2.1 → regstack-0.2.3}/.python-version +0 -0
  41. {regstack-0.2.1 → regstack-0.2.3}/.readthedocs.yaml +0 -0
  42. {regstack-0.2.1 → regstack-0.2.3}/CLAUDE.md +0 -0
  43. {regstack-0.2.1 → regstack-0.2.3}/LICENSE +0 -0
  44. {regstack-0.2.1 → regstack-0.2.3}/NOTICE +0 -0
  45. {regstack-0.2.1 → regstack-0.2.3}/SECURITY.md +0 -0
  46. {regstack-0.2.1 → regstack-0.2.3}/docs/_static/.gitkeep +0 -0
  47. {regstack-0.2.1 → regstack-0.2.3}/docs/_templates/.gitkeep +0 -0
  48. {regstack-0.2.1 → regstack-0.2.3}/docs/cli.md +0 -0
  49. {regstack-0.2.1 → regstack-0.2.3}/docs/conf.py +0 -0
  50. {regstack-0.2.1 → regstack-0.2.3}/docs/configuration.md +0 -0
  51. {regstack-0.2.1 → regstack-0.2.3}/docs/theming.md +0 -0
  52. {regstack-0.2.1 → regstack-0.2.3}/examples/_common/__init__.py +0 -0
  53. {regstack-0.2.1 → regstack-0.2.3}/examples/_common/app.py +0 -0
  54. {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/README.md +0 -0
  55. {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/branding/theme.css +0 -0
  56. {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/main.py +0 -0
  57. {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/regstack.toml +0 -0
  58. {regstack-0.2.1 → regstack-0.2.3}/examples/postgres/README.md +0 -0
  59. {regstack-0.2.1 → regstack-0.2.3}/examples/postgres/main.py +0 -0
  60. {regstack-0.2.1 → regstack-0.2.3}/examples/postgres/regstack.toml +0 -0
  61. {regstack-0.2.1 → regstack-0.2.3}/examples/sqlite/README.md +0 -0
  62. {regstack-0.2.1 → regstack-0.2.3}/examples/sqlite/main.py +0 -0
  63. {regstack-0.2.1 → regstack-0.2.3}/examples/sqlite/regstack.toml +0 -0
  64. {regstack-0.2.1 → regstack-0.2.3}/regstack.toml.example +0 -0
  65. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/__init__.py +0 -0
  66. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/__init__.py +0 -0
  67. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/mfa.py +0 -0
  68. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/tokens.py +0 -0
  69. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/__init__.py +0 -0
  70. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/base.py +0 -0
  71. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/factory.py +0 -0
  72. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/__init__.py +0 -0
  73. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/backend.py +0 -0
  74. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/client.py +0 -0
  75. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/indexes.py +0 -0
  76. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
  77. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
  78. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
  79. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
  80. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -0
  81. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
  82. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/__init__.py +0 -0
  83. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/backend.py +0 -0
  84. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/__init__.py +0 -0
  85. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/env.py +0 -0
  86. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
  87. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
  88. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/__init__.py +0 -0
  89. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
  90. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
  91. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
  92. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
  93. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
  94. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/schema.py +0 -0
  95. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/types.py +0 -0
  96. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/__init__.py +0 -0
  97. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/__main__.py +0 -0
  98. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/_runtime.py +0 -0
  99. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/admin.py +0 -0
  100. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/doctor.py +0 -0
  101. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/init.py +0 -0
  102. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/migrate.py +0 -0
  103. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/__init__.py +0 -0
  104. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/loader.py +0 -0
  105. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/schema.py +0 -0
  106. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/secrets.py +0 -0
  107. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/__init__.py +0 -0
  108. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/composer.py +0 -0
  109. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/console.py +0 -0
  110. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/factory.py +0 -0
  111. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/ses.py +0 -0
  112. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/smtp.py +0 -0
  113. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/email_change.html +0 -0
  114. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/email_change.subject.txt +0 -0
  115. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/email_change.txt +0 -0
  116. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/password_reset.html +0 -0
  117. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/password_reset.subject.txt +0 -0
  118. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/password_reset.txt +0 -0
  119. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
  120. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
  121. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/verification.html +0 -0
  122. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/verification.subject.txt +0 -0
  123. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/verification.txt +0 -0
  124. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/hooks/__init__.py +0 -0
  125. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/__init__.py +0 -0
  126. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/_objectid.py +0 -0
  127. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/login_attempt.py +0 -0
  128. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/mfa_code.py +0 -0
  129. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/pending_registration.py +0 -0
  130. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/user.py +0 -0
  131. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/_schemas.py +0 -0
  132. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/account.py +0 -0
  133. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/admin.py +0 -0
  134. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/login.py +0 -0
  135. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/logout.py +0 -0
  136. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/password.py +0 -0
  137. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/phone.py +0 -0
  138. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/register.py +0 -0
  139. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/verify.py +0 -0
  140. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/__init__.py +0 -0
  141. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/factory.py +0 -0
  142. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/null.py +0 -0
  143. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/sns.py +0 -0
  144. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/twilio.py +0 -0
  145. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/__init__.py +0 -0
  146. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/static/css/core.css +0 -0
  147. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/static/css/theme.css +0 -0
  148. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/static/js/regstack.js +0 -0
  149. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
  150. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/forgot.html +0 -0
  151. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/login.html +0 -0
  152. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/me.html +0 -0
  153. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
  154. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/register.html +0 -0
  155. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/reset.html +0 -0
  156. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/verify.html +0 -0
  157. {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/base.html +0 -0
  158. {regstack-0.2.1 → regstack-0.2.3}/tasks.py +0 -0
  159. {regstack-0.2.1 → regstack-0.2.3}/tests/__init__.py +0 -0
  160. {regstack-0.2.1 → regstack-0.2.3}/tests/conftest.py +0 -0
  161. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/__init__.py +0 -0
  162. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_account_management.py +0 -0
  163. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_admin_router.py +0 -0
  164. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_happy_path.py +0 -0
  165. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_indexes.py +0 -0
  166. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_login_lockout.py +0 -0
  167. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_mfa.py +0 -0
  168. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_password_reset.py +0 -0
  169. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_sql_migrations.py +0 -0
  170. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_ui_router.py +0 -0
  171. {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_verification.py +0 -0
  172. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/__init__.py +0 -0
  173. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_base_install_imports.py +0 -0
  174. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_cli.py +0 -0
  175. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_config_loader.py +0 -0
  176. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_jwt.py +0 -0
  177. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_lockout.py +0 -0
  178. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_mail_composer.py +0 -0
  179. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_mfa_code_repo.py +0 -0
  180. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_password.py +0 -0
  181. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_ses_backend.py +0 -0
  182. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_sms.py +0 -0
  183. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_smtp_backend.py +0 -0
  184. {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_ui_env.py +0 -0
@@ -5,6 +5,26 @@ authoritative copy lives at
5
5
  [`docs/changelog.md`](docs/changelog.md) and is rendered into the
6
6
  Sphinx docs.
7
7
 
8
+ ## 0.2.3 — 2026-04-28
9
+
10
+ Docs-only release. Restructured the API reference around the current
11
+ package layout (post multi-backend refactor) and added Google-style
12
+ docstrings (Args / Returns / Raises) to the public surface — RegStack,
13
+ JwtCodec, PasswordHasher, LockoutService, AuthDependencies,
14
+ HookRegistry, EmailService, SmsService, the router builders, and the
15
+ Clock implementations. Dataclass field docs moved to PEP 258
16
+ attribute docstrings. Sphinx builds clean under `-W` again.
17
+
18
+ ## 0.2.2 — 2026-04-28
19
+
20
+ Docs-only release. The README and Sphinx docs landing page now lead
21
+ with the same pitch (problem framing, "Why not just use…?" comparison
22
+ vs Auth0 / Clerk / Keycloak / fastapi-users) before diving into
23
+ architecture. Hyperlink density trimmed back: only major external
24
+ packages, products, and JWT (RFC 7519) are linked — Wikipedia trivia,
25
+ MDN basics, OWASP article links, and deep-dependency helper-class
26
+ docs were removed.
27
+
8
28
  ## 0.2.1 — 2026-04-28
9
29
 
10
30
  Hotfix for 0.2.0: `import regstack` failed on a base install because
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: regstack
3
- Version: 0.2.1
3
+ Version: 0.2.3
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
@@ -96,17 +96,15 @@ the admin panel, lock out brute-force attackers, and ideally a second
96
96
  factor. Every one of those endpoints has a well-known way to get
97
97
  subtly wrong:
98
98
 
99
- - **Password hashing.** Use [Argon2](https://en.wikipedia.org/wiki/Argon2)
100
- (the [PHC winner](https://www.password-hashing.net/)), not MD5, SHA-1,
101
- bcrypt-without-pepper, or — somehow still common — plain text.
99
+ - **Password hashing.** Use Argon2id, not MD5, SHA-1, bcrypt-without-pepper,
100
+ or somehow still common — plain text.
102
101
  - **Token revocation.** A [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
103
102
  is signed and self-contained: the server can't "log it out" unless you
104
103
  build a revocation list. Forget this and a stolen token works until it
105
104
  expires.
106
105
  - **Account enumeration.** A login or password-reset endpoint that
107
106
  responds differently for "user exists" vs "user doesn't" lets an
108
- attacker harvest your customer list. See
109
- [OWASP WSTG-IDNT-04](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/03-Identity_Management_Testing/04-Testing_for_Account_Enumeration_and_Guessable_User_Account).
107
+ attacker harvest your customer list.
110
108
  - **Bulk session invalidation.** When a user changes their password
111
109
  because they think they were compromised, every existing token they
112
110
  hold should stop working immediately. Most homegrown JWT layers don't
@@ -115,9 +113,9 @@ subtly wrong:
115
113
  random, hashed at rest, single-use, and expire fast. Storing the raw
116
114
  token in the database is a "now your DB backup is also a credential
117
115
  dump" mistake.
118
- - **Phone numbers.** SMS codes need [E.164](https://en.wikipedia.org/wiki/E.164)-validated
119
- numbers, attempt limits, and an upstream provider. Wiring all of that
120
- yourself for a single feature is rarely worth it.
116
+ - **Phone numbers.** SMS codes need E.164-validated numbers, attempt
117
+ limits, and an upstream provider. Wiring all of that yourself for a
118
+ single feature is rarely worth it.
121
119
 
122
120
  Doing all of these correctly, with tests, is two to four weeks of
123
121
  engineering for a competent team. Doing them once and embedding the
@@ -244,9 +242,9 @@ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdr
244
242
 
245
243
  Alpha. Single-file SQLite is the default and runs with no infrastructure;
246
244
  PostgreSQL and MongoDB backends pass the same parametrized integration
247
- suite. The next tagged release is `v0.2.0`. See the
245
+ suite. Latest tagged release: `v0.2.1`. See the
248
246
  [changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
249
- for the per-milestone breakdown.
247
+ for the per-release breakdown.
250
248
 
251
249
  ## Contributing
252
250
 
@@ -32,17 +32,15 @@ the admin panel, lock out brute-force attackers, and ideally a second
32
32
  factor. Every one of those endpoints has a well-known way to get
33
33
  subtly wrong:
34
34
 
35
- - **Password hashing.** Use [Argon2](https://en.wikipedia.org/wiki/Argon2)
36
- (the [PHC winner](https://www.password-hashing.net/)), not MD5, SHA-1,
37
- bcrypt-without-pepper, or — somehow still common — plain text.
35
+ - **Password hashing.** Use Argon2id, not MD5, SHA-1, bcrypt-without-pepper,
36
+ or somehow still common — plain text.
38
37
  - **Token revocation.** A [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
39
38
  is signed and self-contained: the server can't "log it out" unless you
40
39
  build a revocation list. Forget this and a stolen token works until it
41
40
  expires.
42
41
  - **Account enumeration.** A login or password-reset endpoint that
43
42
  responds differently for "user exists" vs "user doesn't" lets an
44
- attacker harvest your customer list. See
45
- [OWASP WSTG-IDNT-04](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/03-Identity_Management_Testing/04-Testing_for_Account_Enumeration_and_Guessable_User_Account).
43
+ attacker harvest your customer list.
46
44
  - **Bulk session invalidation.** When a user changes their password
47
45
  because they think they were compromised, every existing token they
48
46
  hold should stop working immediately. Most homegrown JWT layers don't
@@ -51,9 +49,9 @@ subtly wrong:
51
49
  random, hashed at rest, single-use, and expire fast. Storing the raw
52
50
  token in the database is a "now your DB backup is also a credential
53
51
  dump" mistake.
54
- - **Phone numbers.** SMS codes need [E.164](https://en.wikipedia.org/wiki/E.164)-validated
55
- numbers, attempt limits, and an upstream provider. Wiring all of that
56
- yourself for a single feature is rarely worth it.
52
+ - **Phone numbers.** SMS codes need E.164-validated numbers, attempt
53
+ limits, and an upstream provider. Wiring all of that yourself for a
54
+ single feature is rarely worth it.
57
55
 
58
56
  Doing all of these correctly, with tests, is two to four weeks of
59
57
  engineering for a competent team. Doing them once and embedding the
@@ -180,9 +178,9 @@ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdr
180
178
 
181
179
  Alpha. Single-file SQLite is the default and runs with no infrastructure;
182
180
  PostgreSQL and MongoDB backends pass the same parametrized integration
183
- suite. The next tagged release is `v0.2.0`. See the
181
+ suite. Latest tagged release: `v0.2.1`. See the
184
182
  [changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
185
- for the per-milestone breakdown.
183
+ for the per-release breakdown.
186
184
 
187
185
  ## Contributing
188
186
 
@@ -0,0 +1,278 @@
1
+ # API reference
2
+
3
+ Auto-generated from docstrings. The most useful entry point is
4
+ [`regstack.RegStack`](#regstack.app.RegStack); everything else hangs
5
+ off it.
6
+
7
+ This page is organized by what you'd reach for, not by package
8
+ hierarchy. Each section starts with a one-paragraph orientation,
9
+ then the ``autoclass`` / ``autofunction`` directives pull the
10
+ docstrings, signatures, and parameter docs straight off the package
11
+ source.
12
+
13
+ ## Top-level
14
+
15
+ The handful of things you import from `regstack` directly:
16
+
17
+ - [`RegStack`](#regstack.app.RegStack) — the embeddable façade.
18
+ - [`RegStackConfig`](#regstack.config.schema.RegStackConfig) — top-level config.
19
+ - [`EmailConfig`](#regstack.config.schema.EmailConfig) — email-backend sub-config.
20
+ - [`SmsConfig`](#regstack.config.schema.SmsConfig) — SMS-backend sub-config.
21
+
22
+ Most embeddings need only `RegStack` and `RegStackConfig`.
23
+
24
+ ## Façade
25
+
26
+ `RegStack` is the embeddable façade. One per FastAPI application;
27
+ hosts mount its `router` and (optionally) `ui_router`. All collaborators
28
+ — password hasher, JWT codec, repos, hooks bus, email and SMS
29
+ services — hang off the instance.
30
+
31
+ ```{eval-rst}
32
+ .. autoclass:: regstack.app.RegStack
33
+ :members:
34
+ :show-inheritance:
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ `RegStackConfig` is a `pydantic-settings` model loaded from
40
+ environment variables (`REGSTACK_*`), an optional
41
+ `regstack.secrets.env`, an optional `regstack.toml`, and programmatic
42
+ kwargs — in that priority order. See the
43
+ [Configuration guide](configuration.md) for every field with its
44
+ default.
45
+
46
+ ```{eval-rst}
47
+ .. autoclass:: regstack.config.schema.RegStackConfig
48
+ :members:
49
+ :show-inheritance:
50
+ :exclude-members: model_config, model_fields, model_computed_fields
51
+
52
+ .. autoclass:: regstack.config.schema.EmailConfig
53
+ :members:
54
+ :show-inheritance:
55
+ :exclude-members: model_config, model_fields, model_computed_fields
56
+
57
+ .. autoclass:: regstack.config.schema.SmsConfig
58
+ :members:
59
+ :show-inheritance:
60
+ :exclude-members: model_config, model_fields, model_computed_fields
61
+
62
+ .. autofunction:: regstack.config.loader.load_config
63
+ ```
64
+
65
+ ## Auth primitives
66
+
67
+ The pieces that make authentication work: password hashing (Argon2id),
68
+ JWT issuance and validation with per-purpose derived keys, login
69
+ lockout, and the FastAPI dependency factory. Hosts rarely instantiate
70
+ these directly — they're built and wired by the `RegStack` constructor
71
+ — but the docstrings on the public methods explain the contract.
72
+
73
+ ```{eval-rst}
74
+ .. autoclass:: regstack.auth.password.PasswordHasher
75
+ :members:
76
+
77
+ .. autoclass:: regstack.auth.jwt.JwtCodec
78
+ :members:
79
+
80
+ .. autoclass:: regstack.auth.jwt.TokenPayload
81
+ :members:
82
+
83
+ .. autoexception:: regstack.auth.jwt.TokenError
84
+
85
+ .. autofunction:: regstack.auth.jwt.is_payload_bulk_revoked
86
+
87
+ .. autoclass:: regstack.auth.lockout.LockoutService
88
+ :members:
89
+
90
+ .. autoclass:: regstack.auth.lockout.LockoutDecision
91
+ :members:
92
+
93
+ .. autoclass:: regstack.auth.dependencies.AuthDependencies
94
+ :members:
95
+ ```
96
+
97
+ ### Time
98
+
99
+ Every time-sensitive operation reads `now()` through a `Clock`. Tests
100
+ inject `FrozenClock` to make assertions deterministic.
101
+
102
+ ```{eval-rst}
103
+ .. autoclass:: regstack.auth.clock.Clock
104
+ :members:
105
+
106
+ .. autoclass:: regstack.auth.clock.SystemClock
107
+ :members:
108
+
109
+ .. autoclass:: regstack.auth.clock.FrozenClock
110
+ :members:
111
+ ```
112
+
113
+ ## Backends
114
+
115
+ regstack ships three storage backends behind one set of `Protocol`
116
+ classes: SQLite (default, no infrastructure), PostgreSQL, and MongoDB.
117
+ The active backend is auto-built from `config.database_url`'s URL
118
+ scheme via `build_backend`. Hosts that need to share a connection pool
119
+ with their own application can pass an explicit `Backend` to the
120
+ `RegStack` constructor.
121
+
122
+ ```{eval-rst}
123
+ .. autoclass:: regstack.backends.base.Backend
124
+ :members:
125
+ :show-inheritance:
126
+
127
+ .. autoclass:: regstack.backends.base.BackendKind
128
+ :members:
129
+
130
+ .. autofunction:: regstack.backends.factory.build_backend
131
+
132
+ .. autoclass:: regstack.backends.mongo.MongoBackend
133
+ :members:
134
+ :show-inheritance:
135
+
136
+ .. autoclass:: regstack.backends.sql.SqlBackend
137
+ :members:
138
+ :show-inheritance:
139
+ ```
140
+
141
+ ### Repository protocols
142
+
143
+ The five repository protocols are the contract every backend
144
+ implements. Routers and services depend only on these — switching
145
+ backends is a wiring change, not a code change.
146
+
147
+ ```{eval-rst}
148
+ .. autoclass:: regstack.backends.protocols.UserRepoProtocol
149
+ :members:
150
+
151
+ .. autoclass:: regstack.backends.protocols.PendingRepoProtocol
152
+ :members:
153
+
154
+ .. autoclass:: regstack.backends.protocols.BlacklistRepoProtocol
155
+ :members:
156
+
157
+ .. autoclass:: regstack.backends.protocols.LoginAttemptRepoProtocol
158
+ :members:
159
+
160
+ .. autoclass:: regstack.backends.protocols.MfaCodeRepoProtocol
161
+ :members:
162
+
163
+ .. autoclass:: regstack.backends.protocols.MfaVerifyOutcome
164
+ :members:
165
+
166
+ .. autoclass:: regstack.backends.protocols.MfaVerifyResult
167
+ :members:
168
+
169
+ .. autoexception:: regstack.backends.protocols.UserAlreadyExistsError
170
+
171
+ .. autoexception:: regstack.backends.protocols.PendingAlreadyExistsError
172
+ ```
173
+
174
+ ## Models
175
+
176
+ The persisted data shapes. `BaseUser` is the canonical user document;
177
+ `UserCreate` validates registration input; `UserPublic` is what the
178
+ API returns (omits the password hash). The other models drive
179
+ verification, lockout, and SMS MFA.
180
+
181
+ ```{eval-rst}
182
+ .. autoclass:: regstack.models.user.BaseUser
183
+ :members:
184
+ :exclude-members: model_config, model_fields, model_computed_fields
185
+
186
+ .. autoclass:: regstack.models.user.UserCreate
187
+ :members:
188
+ :exclude-members: model_config, model_fields, model_computed_fields
189
+
190
+ .. autoclass:: regstack.models.user.UserPublic
191
+ :members:
192
+ :exclude-members: model_config, model_fields, model_computed_fields
193
+
194
+ .. autoclass:: regstack.models.pending_registration.PendingRegistration
195
+ :members:
196
+ :exclude-members: model_config, model_fields, model_computed_fields
197
+
198
+ .. autoclass:: regstack.models.login_attempt.LoginAttempt
199
+ :members:
200
+ :exclude-members: model_config, model_fields, model_computed_fields
201
+
202
+ .. autoclass:: regstack.models.mfa_code.MfaCode
203
+ :members:
204
+ :exclude-members: model_config, model_fields, model_computed_fields
205
+ ```
206
+
207
+ ## Email + SMS
208
+
209
+ Pluggable transports for the verification / reset / change-email
210
+ emails and the SMS MFA codes. Implement `EmailService` or `SmsService`
211
+ to plug in a provider that isn't bundled (Postmark, SendGrid,
212
+ MessageBird, …) and pass the instance to `regstack.set_email_backend`
213
+ / `set_sms_backend`.
214
+
215
+ ### Email
216
+
217
+ ```{eval-rst}
218
+ .. autoclass:: regstack.email.base.EmailMessage
219
+ :members:
220
+
221
+ .. autoclass:: regstack.email.base.EmailService
222
+ :members:
223
+
224
+ .. autoclass:: regstack.email.console.ConsoleEmailService
225
+ :members:
226
+
227
+ .. autoclass:: regstack.email.composer.MailComposer
228
+ :members:
229
+
230
+ .. autofunction:: regstack.email.factory.build_email_service
231
+ ```
232
+
233
+ ### SMS
234
+
235
+ ```{eval-rst}
236
+ .. autoclass:: regstack.sms.base.SmsMessage
237
+ :members:
238
+
239
+ .. autoclass:: regstack.sms.base.SmsService
240
+ :members:
241
+
242
+ .. autofunction:: regstack.sms.base.is_valid_e164
243
+
244
+ .. autoclass:: regstack.sms.null.NullSmsService
245
+ :members:
246
+
247
+ .. autofunction:: regstack.sms.factory.build_sms_service
248
+ ```
249
+
250
+ ## Hooks
251
+
252
+ The event bus regstack uses to fire side-effect notifications
253
+ (`user_registered`, `password_changed`, etc.) without coupling auth
254
+ code to host concerns like CRMs or analytics. See the
255
+ [Embedding guide](embedding.md#subscribing-to-events) for examples.
256
+
257
+ ```{eval-rst}
258
+ .. autoclass:: regstack.hooks.events.HookRegistry
259
+ :members:
260
+
261
+ .. autodata:: regstack.hooks.events.KNOWN_EVENTS
262
+ ```
263
+
264
+ ## Routers
265
+
266
+ Hosts normally access these via the `regstack.router` and
267
+ `regstack.ui_router` properties; the builder functions are public for
268
+ hosts that want to compose differently.
269
+
270
+ ```{eval-rst}
271
+ .. autofunction:: regstack.routers.build_router
272
+
273
+ .. autofunction:: regstack.ui.pages.build_ui_router
274
+
275
+ .. autofunction:: regstack.ui.pages.build_ui_environment
276
+
277
+ .. autofunction:: regstack.ui.pages.default_static_dir
278
+ ```
@@ -5,10 +5,11 @@ who wants to embed it, extend it, or contribute to it. If you only
5
5
  want to use it, [Quickstart](quickstart.md) is shorter.
6
6
 
7
7
  regstack is a single embeddable façade — `RegStack` — that wires
8
- together storage, password and JWT primitives, an email service, an
9
- SMS service, a [hooks bus](#hooks), and a [FastAPI](https://fastapi.tiangolo.com/)
10
- router. Hosts construct one façade per application and mount its
11
- router(s) wherever they like.
8
+ together storage, password and [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
9
+ primitives, an email service, an SMS service, a [hooks bus](#hooks),
10
+ and a [FastAPI](https://fastapi.tiangolo.com/) router. Hosts
11
+ construct one façade per application and mount its router(s) wherever
12
+ they like.
12
13
 
13
14
  ```text
14
15
  ┌────────────────────────────────────────────────┐
@@ -36,10 +37,9 @@ router(s) wherever they like.
36
37
  └────────┘ └──────────┘ └──────────┘
37
38
  ```
38
39
 
39
- The pattern is the
40
- [façade pattern](https://en.wikipedia.org/wiki/Facade_pattern): one
41
- object that owns and exposes a coherent set of related sub-systems,
42
- so the host has a single import to learn.
40
+ The pattern is the façade pattern: one object that owns and exposes
41
+ a coherent set of related sub-systems, so the host has a single
42
+ import to learn.
43
43
 
44
44
  ## One façade per process
45
45
 
@@ -48,8 +48,7 @@ sms_service=…, mail_composer=…)` is the only public constructor.
48
48
  Everything downstream (routers, dependencies, repos) takes its
49
49
  dependencies from this instance, so you can run **two regstack
50
50
  instances in the same process** without shared state — useful for
51
- [multi-tenant](https://en.wikipedia.org/wiki/Multitenancy) deployments
52
- where a single FastAPI app serves multiple
51
+ multi-tenant deployments where a single FastAPI app serves multiple
53
52
  `<host, database, branding>` triples.
54
53
 
55
54
  The backend is auto-built from `config.database_url` if not supplied
@@ -64,9 +63,7 @@ The façade exposes:
64
63
  - `router` — the JSON `APIRouter` to mount under `config.api_prefix`.
65
64
  - `ui_router` — the SSR `APIRouter` (built on first access; only
66
65
  meaningful when `enable_ui_router=True`).
67
- - `static_files` — Starlette
68
- [`StaticFiles`](https://www.starlette.io/staticfiles/) over the
69
- bundled CSS/JS.
66
+ - `static_files` — Starlette `StaticFiles` over the bundled CSS/JS.
70
67
  - `deps` — `AuthDependencies` factory for `current_user` /
71
68
  `current_admin` (each call returns a closure-bound dep).
72
69
  - `users`, `pending`, `blacklist`, `attempts`, `mfa_codes` — repos.
@@ -75,7 +72,7 @@ The façade exposes:
75
72
  - `backend` — the active `regstack.backends.base.Backend`.
76
73
  - `install_schema()` — install indexes (Mongo) or run
77
74
  [Alembic](https://alembic.sqlalchemy.org/) migrations (SQL).
78
- `install_indexes()` is kept as a backwards-compat alias.
75
+ `install_indexes()` is kept as a back-compat alias.
79
76
  - `aclose()` — tear down the backend's connection pool.
80
77
  - `bootstrap_admin(email, password)`,
81
78
  `add_template_dir(path)`, `set_email_backend(...)`,
@@ -85,9 +82,8 @@ The façade exposes:
85
82
 
86
83
  The Backend ABC owns the persistence story. Each backend ships:
87
84
 
88
- - One concrete repository per
89
- [Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol):
90
- `UserRepoProtocol`, `PendingRepoProtocol`, `BlacklistRepoProtocol`,
85
+ - One concrete repository per Protocol: `UserRepoProtocol`,
86
+ `PendingRepoProtocol`, `BlacklistRepoProtocol`,
91
87
  `LoginAttemptRepoProtocol`, `MfaCodeRepoProtocol`.
92
88
  - `install_schema()` to create indexes (Mongo) or run table creation /
93
89
  Alembic migrations (SQL).
@@ -95,16 +91,15 @@ The Backend ABC owns the persistence story. Each backend ships:
95
91
  - `aclose()` for clean shutdown.
96
92
 
97
93
  The Mongo backend lives at `regstack.backends.mongo`; the SQL backend
98
- (driving both SQLite and Postgres via [SQLAlchemy 2 async](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html))
99
- lives at `regstack.backends.sql`. Both are routed via the
94
+ (driving both SQLite and Postgres via [SQLAlchemy](https://www.sqlalchemy.org/)
95
+ async) lives at `regstack.backends.sql`. Both are routed via the
100
96
  `regstack.backends.factory.build_backend(config)` factory.
101
97
 
102
98
  ### TTL handling differences
103
99
 
104
- Mongo gets free expiry via [TTL indexes](https://www.mongodb.com/docs/manual/core/index-ttl/)
105
- `pending_registrations`, `token_blacklist`, `login_attempts`, and
106
- `mfa_codes` all have `expireAfterSeconds` indexes that the Mongo
107
- background task reaps.
100
+ Mongo gets free expiry via TTL indexes — `pending_registrations`,
101
+ `token_blacklist`, `login_attempts`, and `mfa_codes` all have
102
+ `expireAfterSeconds` indexes that the Mongo background task reaps.
108
103
 
109
104
  The SQL backends have no equivalent. Two safety nets:
110
105
 
@@ -112,22 +107,21 @@ The SQL backends have no equivalent. Two safety nets:
112
107
  checks `expires_at > now()` (or equivalent). A stale row in the
113
108
  table is harmless because it's never returned.
114
109
  - **Periodic reaper**: each repo exposes `purge_expired(...)`. Hosts
115
- that care about disk usage can run it on a schedule (e.g. via
116
- [APScheduler](https://apscheduler.readthedocs.io/) or a
117
- `regstack reap` cron job).
110
+ that care about disk usage can run it on a schedule (e.g. a cron
111
+ job calling a small `regstack reap` script).
118
112
 
119
113
  This means SQL backends are functionally correct without the reaper,
120
114
  but accumulate dead rows over time. Mongo doesn't.
121
115
 
122
116
  ## Repositories
123
117
 
124
- Each backend ships a thin async repo per collection / table. The Mongo
125
- repos are tz-aware because `make_client` configures
118
+ Each backend ships a thin async repo per collection / table. The
119
+ Mongo repos are tz-aware because `make_client` configures
126
120
  `AsyncMongoClient(..., tz_aware=True)`; the SQL repos use a custom
127
- `UtcDateTime` [TypeDecorator](https://docs.sqlalchemy.org/en/20/core/custom_types.html#sqlalchemy.types.TypeDecorator)
128
- that stores UTC and re-attaches the UTC tzinfo on read. Every layer
129
- above the repo assumes UTC-aware datetimes — there is no naive
130
- datetime anywhere in the public API.
121
+ `UtcDateTime` SQLAlchemy `TypeDecorator` that stores UTC and
122
+ re-attaches the UTC tzinfo on read. Every layer above the repo
123
+ assumes UTC-aware datetimes — there is no naive datetime anywhere in
124
+ the public API.
131
125
 
132
126
  `UserRepo` accepts an injected `Clock`; bulk-revoke writes
133
127
  (`update_password`, `update_email`, `set_tokens_invalidated_after`)
@@ -179,20 +173,18 @@ one mechanism:
179
173
  - `build_ui_environment` — SSR HTML templates under
180
174
  `regstack/ui/templates/`.
181
175
 
182
- Both wrap a
183
- [`ChoiceLoader([host_dirs..., regstack_default])`](https://jinja.palletsprojects.com/en/stable/api/#jinja2.ChoiceLoader)
184
- so a host override drops a same-named file into its template
185
- directory and wins over the bundled version.
186
- `RegStack.add_template_dir(path)` feeds both loaders simultaneously.
176
+ Both wrap a `ChoiceLoader([host_dirs..., regstack_default])` so a
177
+ host override drops a same-named file into its template directory and
178
+ wins over the bundled version. `RegStack.add_template_dir(path)`
179
+ feeds both loaders simultaneously.
187
180
 
188
181
  ## CLI runtime
189
182
 
190
183
  `regstack init`, `regstack create-admin`, and `regstack doctor` share
191
- `cli/_runtime.py`. `open_regstack(toml_path=None)` is an
192
- [async context manager](https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager)
193
- that builds a real RegStack against a real backend, runs
194
- `install_schema()`, and tears the connection down on exit — the right
195
- pattern for a short-lived CLI invocation.
184
+ `cli/_runtime.py`. `open_regstack(toml_path=None)` is an async
185
+ context manager that builds a real RegStack against a real backend,
186
+ runs `install_schema()`, and tears the connection down on exit — the
187
+ right pattern for a short-lived CLI invocation.
196
188
 
197
189
  ## Testing seams
198
190
 
@@ -206,5 +198,4 @@ pattern for a short-lived CLI invocation.
206
198
  test build multiple `RegStack` instances against per-worker DBs to
207
199
  exercise different config combinations without leaking. The
208
200
  parametrized `backend_kind` fixture runs every integration test
209
- against every active backend in parallel via
210
- [`pytest-xdist`](https://pytest-xdist.readthedocs.io/).
201
+ against every active backend in parallel via pytest-xdist.
@@ -3,6 +3,59 @@
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.2.3 — 2026-04-28
7
+
8
+ **Docs-only release.** API reference rewritten around the current
9
+ package layout, public surface gained proper Google-style docstrings.
10
+
11
+ ### Changed
12
+
13
+ - ``docs/api.md`` restructured around the post-multi-backend package
14
+ layout (``regstack.backends.{base,protocols,factory,mongo,sql}`` and
15
+ friends). Each section now opens with a one-paragraph orientation
16
+ before the autodoc directives. The pre-refactor
17
+ ``regstack.db.repositories.*`` references that rendered empty are
18
+ gone.
19
+ - Added Google-style docstrings (purpose summary + Args / Returns /
20
+ Raises) to the most-touched public methods on ``RegStack``,
21
+ ``JwtCodec``, ``PasswordHasher``, ``LockoutService``,
22
+ ``AuthDependencies``, ``HookRegistry``, ``EmailService``,
23
+ ``SmsService``, ``build_router``, ``build_ui_router``,
24
+ ``build_ui_environment``, ``default_static_dir``, ``Clock`` /
25
+ ``SystemClock`` / ``FrozenClock``.
26
+ - Dataclass field documentation moved to PEP 258 attribute docstrings
27
+ on ``TokenPayload``, ``LockoutDecision``, ``EmailMessage``,
28
+ ``SmsMessage``, ``MfaVerifyResult`` — autodoc now renders each field
29
+ with its description without the "duplicate object description"
30
+ warnings the napoleon ``Attributes:`` block was triggering.
31
+ - ``MfaVerifyOutcome`` enum docstring reformatted as a bullet list
32
+ (the napoleon ``Members:`` block isn't a recognised section).
33
+
34
+ ## 0.2.2 — 2026-04-28
35
+
36
+ **Docs-only release.**
37
+
38
+ ### Changed
39
+
40
+ - README and `docs/index.md` both now lead with the same pitch — a
41
+ tagline ("Production-grade user accounts for your FastAPI app —
42
+ without the vendor lock-in, the second service to run, or the
43
+ homegrown auth bugs"), a "The problem regstack solves" section
44
+ (Argon2, JWT revocation, account enumeration, bulk session
45
+ invalidation, hashed one-time tokens, E.164 phone numbers), and a
46
+ "Why not just use…?" comparison table covering hosted SaaS
47
+ (Auth0 / Clerk / WorkOS / Stytch), self-hosted IAM (Keycloak /
48
+ Authentik / Authelia / Ory Kratos), `fastapi-users`, and DIY.
49
+ - Trimmed hyperlink density back. Only major external packages,
50
+ products, and JWT (RFC 7519) are linked. Wikipedia articles on
51
+ CS concepts (façade pattern, multitenancy, idempotence, E.164,
52
+ SHA-256, HMAC), MDN web platform basics (CSP, fetch, localStorage,
53
+ HTTP 429, Retry-After, HTTPS, CSS custom properties), OWASP article
54
+ links, Python stdlib pages, and deep-dependency helper-class docs
55
+ (pwdlib, pydantic, asyncpg, pymongo, ChoiceLoader, TypeDecorator,
56
+ StaticFiles, ProxyHeadersMiddleware, slowapi, APScheduler,
57
+ pytest-xdist, Kubernetes probes) were removed.
58
+
6
59
  ## 0.2.1 — 2026-04-28
7
60
 
8
61
  **Hotfix for 0.2.0.** `import regstack` was broken on any install that
@@ -110,9 +110,8 @@ regstack.add_template_dir(Path("/app/host/templates"))
110
110
  ```
111
111
 
112
112
  Drop a same-named file into your directory to win against the bundled
113
- default — regstack uses Jinja2's
114
- [`ChoiceLoader`](https://jinja.palletsprojects.com/en/stable/api/#jinja2.ChoiceLoader)
115
- so the host directory is searched first. Examples:
113
+ default — regstack uses Jinja2's `ChoiceLoader` so the host directory
114
+ is searched first. Examples:
116
115
 
117
116
  - `auth/login.html` — replaces the SSR sign-in page.
118
117
  - `verification.html` / `verification.txt` /
@@ -125,8 +124,8 @@ A list of every overridable file lives in
125
124
  ## Switching the SSR theme without templates
126
125
 
127
126
  If you only want to flip colors / fonts, you don't need to override
128
- any templates — just supply a CSS file that overrides the
129
- [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*):
127
+ any templates — just supply a CSS file that overrides the bundled
128
+ CSS custom properties:
130
129
 
131
130
  ```toml
132
131
  # regstack.toml
@@ -171,8 +170,8 @@ In code:
171
170
  await regstack.bootstrap_admin("admin@example.com", "long-strong-password")
172
171
  ```
173
172
 
174
- This is [idempotent](https://en.wikipedia.org/wiki/Idempotence)
175
- promotes an existing user, creates one if not present.
173
+ This is idempotent — promotes an existing user, creates one if not
174
+ present.
176
175
 
177
176
  ## Health-check and probes
178
177
 
@@ -183,16 +182,14 @@ uv run regstack doctor [--config ...] [--check-dns] [--send-test-email <addr>]
183
182
  `doctor` reports JWT secret strength, database reachability, indexes,
184
183
  the email backend's instantiability, and optionally DNS (SPF/DKIM/MX)
185
184
  and a real email send. Exit code is the number of failed checks —
186
- wire it into a container health check or a
187
- [Kubernetes liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
185
+ wire it into a container health check or a Kubernetes liveness probe
188
186
  for production probes that need more than a TCP hit.
189
187
 
190
188
  ## What regstack does *not* do
191
189
 
192
- - It does not mount a [CSRF](https://owasp.org/www-community/attacks/csrf)
193
- middleware. The bundled SSR pages don't use cookies, so they don't
194
- need it; if you swap the bundled JS for a cookie-based variant,
195
- configure CSRF at the host.
190
+ - It does not mount a CSRF middleware. The bundled SSR pages don't
191
+ use cookies, so they don't need it; if you swap the bundled JS for
192
+ a cookie-based variant, configure CSRF at the host.
196
193
  - It does not enforce HTTPS. Run behind a TLS terminator.
197
194
  - It does not provision SES identities, Route 53 records, IAM users,
198
195
  or anything else outside the database.