regstack 0.2.1__tar.gz → 0.2.2__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 (175) hide show
  1. {regstack-0.2.1 → regstack-0.2.2}/CHANGELOG.md +10 -0
  2. {regstack-0.2.1 → regstack-0.2.2}/PKG-INFO +9 -11
  3. {regstack-0.2.1 → regstack-0.2.2}/README.md +8 -10
  4. {regstack-0.2.1 → regstack-0.2.2}/docs/architecture.md +35 -44
  5. {regstack-0.2.1 → regstack-0.2.2}/docs/changelog.md +25 -0
  6. {regstack-0.2.1 → regstack-0.2.2}/docs/embedding.md +10 -13
  7. regstack-0.2.2/docs/index.md +141 -0
  8. {regstack-0.2.1 → regstack-0.2.2}/docs/quickstart.md +11 -11
  9. {regstack-0.2.1 → regstack-0.2.2}/docs/security.md +49 -68
  10. {regstack-0.2.1 → regstack-0.2.2}/pyproject.toml +1 -1
  11. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/dependencies.py +3 -4
  12. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/lockout.py +2 -2
  13. regstack-0.2.2/src/regstack/version.py +1 -0
  14. {regstack-0.2.1 → regstack-0.2.2}/uv.lock +1 -1
  15. regstack-0.2.1/docs/index.md +0 -99
  16. regstack-0.2.1/src/regstack/version.py +0 -1
  17. {regstack-0.2.1 → regstack-0.2.2}/.github/workflows/publish.yml +0 -0
  18. {regstack-0.2.1 → regstack-0.2.2}/.github/workflows/test.yml +0 -0
  19. {regstack-0.2.1 → regstack-0.2.2}/.gitignore +0 -0
  20. {regstack-0.2.1 → regstack-0.2.2}/.python-version +0 -0
  21. {regstack-0.2.1 → regstack-0.2.2}/.readthedocs.yaml +0 -0
  22. {regstack-0.2.1 → regstack-0.2.2}/CLAUDE.md +0 -0
  23. {regstack-0.2.1 → regstack-0.2.2}/LICENSE +0 -0
  24. {regstack-0.2.1 → regstack-0.2.2}/NOTICE +0 -0
  25. {regstack-0.2.1 → regstack-0.2.2}/SECURITY.md +0 -0
  26. {regstack-0.2.1 → regstack-0.2.2}/docs/_static/.gitkeep +0 -0
  27. {regstack-0.2.1 → regstack-0.2.2}/docs/_templates/.gitkeep +0 -0
  28. {regstack-0.2.1 → regstack-0.2.2}/docs/api.md +0 -0
  29. {regstack-0.2.1 → regstack-0.2.2}/docs/cli.md +0 -0
  30. {regstack-0.2.1 → regstack-0.2.2}/docs/conf.py +0 -0
  31. {regstack-0.2.1 → regstack-0.2.2}/docs/configuration.md +0 -0
  32. {regstack-0.2.1 → regstack-0.2.2}/docs/theming.md +0 -0
  33. {regstack-0.2.1 → regstack-0.2.2}/examples/_common/__init__.py +0 -0
  34. {regstack-0.2.1 → regstack-0.2.2}/examples/_common/app.py +0 -0
  35. {regstack-0.2.1 → regstack-0.2.2}/examples/mongo/README.md +0 -0
  36. {regstack-0.2.1 → regstack-0.2.2}/examples/mongo/branding/theme.css +0 -0
  37. {regstack-0.2.1 → regstack-0.2.2}/examples/mongo/main.py +0 -0
  38. {regstack-0.2.1 → regstack-0.2.2}/examples/mongo/regstack.toml +0 -0
  39. {regstack-0.2.1 → regstack-0.2.2}/examples/postgres/README.md +0 -0
  40. {regstack-0.2.1 → regstack-0.2.2}/examples/postgres/main.py +0 -0
  41. {regstack-0.2.1 → regstack-0.2.2}/examples/postgres/regstack.toml +0 -0
  42. {regstack-0.2.1 → regstack-0.2.2}/examples/sqlite/README.md +0 -0
  43. {regstack-0.2.1 → regstack-0.2.2}/examples/sqlite/main.py +0 -0
  44. {regstack-0.2.1 → regstack-0.2.2}/examples/sqlite/regstack.toml +0 -0
  45. {regstack-0.2.1 → regstack-0.2.2}/regstack.toml.example +0 -0
  46. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/__init__.py +0 -0
  47. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/app.py +0 -0
  48. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/__init__.py +0 -0
  49. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/clock.py +0 -0
  50. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/jwt.py +0 -0
  51. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/mfa.py +0 -0
  52. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/password.py +0 -0
  53. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/auth/tokens.py +0 -0
  54. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/__init__.py +0 -0
  55. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/base.py +0 -0
  56. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/factory.py +0 -0
  57. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/__init__.py +0 -0
  58. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/backend.py +0 -0
  59. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/client.py +0 -0
  60. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/indexes.py +0 -0
  61. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
  62. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
  63. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
  64. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
  65. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -0
  66. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
  67. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/protocols.py +0 -0
  68. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/__init__.py +0 -0
  69. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/backend.py +0 -0
  70. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/migrations/__init__.py +0 -0
  71. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/migrations/env.py +0 -0
  72. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
  73. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
  74. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/repositories/__init__.py +0 -0
  75. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
  76. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
  77. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
  78. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
  79. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
  80. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/schema.py +0 -0
  81. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/backends/sql/types.py +0 -0
  82. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/__init__.py +0 -0
  83. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/__main__.py +0 -0
  84. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/_runtime.py +0 -0
  85. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/admin.py +0 -0
  86. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/doctor.py +0 -0
  87. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/init.py +0 -0
  88. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/cli/migrate.py +0 -0
  89. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/config/__init__.py +0 -0
  90. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/config/loader.py +0 -0
  91. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/config/schema.py +0 -0
  92. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/config/secrets.py +0 -0
  93. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/__init__.py +0 -0
  94. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/base.py +0 -0
  95. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/composer.py +0 -0
  96. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/console.py +0 -0
  97. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/factory.py +0 -0
  98. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/ses.py +0 -0
  99. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/smtp.py +0 -0
  100. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/email_change.html +0 -0
  101. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/email_change.subject.txt +0 -0
  102. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/email_change.txt +0 -0
  103. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/password_reset.html +0 -0
  104. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/password_reset.subject.txt +0 -0
  105. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/password_reset.txt +0 -0
  106. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
  107. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
  108. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/verification.html +0 -0
  109. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/verification.subject.txt +0 -0
  110. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/email/templates/verification.txt +0 -0
  111. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/hooks/__init__.py +0 -0
  112. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/hooks/events.py +0 -0
  113. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/models/__init__.py +0 -0
  114. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/models/_objectid.py +0 -0
  115. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/models/login_attempt.py +0 -0
  116. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/models/mfa_code.py +0 -0
  117. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/models/pending_registration.py +0 -0
  118. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/models/user.py +0 -0
  119. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/__init__.py +0 -0
  120. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/_schemas.py +0 -0
  121. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/account.py +0 -0
  122. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/admin.py +0 -0
  123. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/login.py +0 -0
  124. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/logout.py +0 -0
  125. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/password.py +0 -0
  126. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/phone.py +0 -0
  127. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/register.py +0 -0
  128. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/routers/verify.py +0 -0
  129. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/sms/__init__.py +0 -0
  130. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/sms/base.py +0 -0
  131. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/sms/factory.py +0 -0
  132. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/sms/null.py +0 -0
  133. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/sms/sns.py +0 -0
  134. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/sms/twilio.py +0 -0
  135. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/__init__.py +0 -0
  136. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/pages.py +0 -0
  137. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/static/css/core.css +0 -0
  138. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/static/css/theme.css +0 -0
  139. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/static/js/regstack.js +0 -0
  140. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
  141. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/forgot.html +0 -0
  142. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/login.html +0 -0
  143. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/me.html +0 -0
  144. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
  145. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/register.html +0 -0
  146. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/reset.html +0 -0
  147. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/auth/verify.html +0 -0
  148. {regstack-0.2.1 → regstack-0.2.2}/src/regstack/ui/templates/base.html +0 -0
  149. {regstack-0.2.1 → regstack-0.2.2}/tasks.py +0 -0
  150. {regstack-0.2.1 → regstack-0.2.2}/tests/__init__.py +0 -0
  151. {regstack-0.2.1 → regstack-0.2.2}/tests/conftest.py +0 -0
  152. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/__init__.py +0 -0
  153. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_account_management.py +0 -0
  154. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_admin_router.py +0 -0
  155. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_happy_path.py +0 -0
  156. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_indexes.py +0 -0
  157. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_login_lockout.py +0 -0
  158. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_mfa.py +0 -0
  159. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_password_reset.py +0 -0
  160. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_sql_migrations.py +0 -0
  161. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_ui_router.py +0 -0
  162. {regstack-0.2.1 → regstack-0.2.2}/tests/integration/test_verification.py +0 -0
  163. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/__init__.py +0 -0
  164. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_base_install_imports.py +0 -0
  165. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_cli.py +0 -0
  166. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_config_loader.py +0 -0
  167. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_jwt.py +0 -0
  168. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_lockout.py +0 -0
  169. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_mail_composer.py +0 -0
  170. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_mfa_code_repo.py +0 -0
  171. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_password.py +0 -0
  172. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_ses_backend.py +0 -0
  173. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_sms.py +0 -0
  174. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_smtp_backend.py +0 -0
  175. {regstack-0.2.1 → regstack-0.2.2}/tests/unit/test_ui_env.py +0 -0
@@ -5,6 +5,16 @@ 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.2 — 2026-04-28
9
+
10
+ Docs-only release. The README and Sphinx docs landing page now lead
11
+ with the same pitch (problem framing, "Why not just use…?" comparison
12
+ vs Auth0 / Clerk / Keycloak / fastapi-users) before diving into
13
+ architecture. Hyperlink density trimmed back: only major external
14
+ packages, products, and JWT (RFC 7519) are linked — Wikipedia trivia,
15
+ MDN basics, OWASP article links, and deep-dependency helper-class
16
+ docs were removed.
17
+
8
18
  ## 0.2.1 — 2026-04-28
9
19
 
10
20
  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.2
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
 
@@ -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,31 @@
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.2 — 2026-04-28
7
+
8
+ **Docs-only release.**
9
+
10
+ ### Changed
11
+
12
+ - README and `docs/index.md` both now lead with the same pitch — a
13
+ tagline ("Production-grade user accounts for your FastAPI app —
14
+ without the vendor lock-in, the second service to run, or the
15
+ homegrown auth bugs"), a "The problem regstack solves" section
16
+ (Argon2, JWT revocation, account enumeration, bulk session
17
+ invalidation, hashed one-time tokens, E.164 phone numbers), and a
18
+ "Why not just use…?" comparison table covering hosted SaaS
19
+ (Auth0 / Clerk / WorkOS / Stytch), self-hosted IAM (Keycloak /
20
+ Authentik / Authelia / Ory Kratos), `fastapi-users`, and DIY.
21
+ - Trimmed hyperlink density back. Only major external packages,
22
+ products, and JWT (RFC 7519) are linked. Wikipedia articles on
23
+ CS concepts (façade pattern, multitenancy, idempotence, E.164,
24
+ SHA-256, HMAC), MDN web platform basics (CSP, fetch, localStorage,
25
+ HTTP 429, Retry-After, HTTPS, CSS custom properties), OWASP article
26
+ links, Python stdlib pages, and deep-dependency helper-class docs
27
+ (pwdlib, pydantic, asyncpg, pymongo, ChoiceLoader, TypeDecorator,
28
+ StaticFiles, ProxyHeadersMiddleware, slowapi, APScheduler,
29
+ pytest-xdist, Kubernetes probes) were removed.
30
+
6
31
  ## 0.2.1 — 2026-04-28
7
32
 
8
33
  **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.
@@ -0,0 +1,141 @@
1
+ # regstack
2
+
3
+ **Production-grade user accounts for your [FastAPI](https://fastapi.tiangolo.com/)
4
+ app — without the vendor lock-in, the second service to run, or the
5
+ homegrown auth bugs.**
6
+
7
+ `pip install regstack`, point it at SQLite (default),
8
+ [PostgreSQL](https://www.postgresql.org/), or
9
+ [MongoDB](https://www.mongodb.com/), and you have register / login /
10
+ verify-email / reset-password / change-email / delete-account /
11
+ optional SMS two-factor / admin endpoints / themeable HTML pages —
12
+ all behind a small Python API and one config file.
13
+
14
+ ## The problem regstack solves
15
+
16
+ Every web application that has users eventually needs the same dozen
17
+ endpoints: register, log in, log out, verify email, reset a forgotten
18
+ password, change password, change email, delete account, list users
19
+ for the admin panel, lock out brute-force attackers, and ideally a
20
+ second factor. Every one of those endpoints has a well-known way to
21
+ get subtly wrong:
22
+
23
+ - **Password hashing.** Use Argon2id, not MD5, SHA-1,
24
+ bcrypt-without-pepper, or — somehow still common — plain text.
25
+ - **Token revocation.** A [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
26
+ is signed and self-contained: the server can't "log it out" unless
27
+ you build a revocation list. Forget this and a stolen token works
28
+ until it expires.
29
+ - **Account enumeration.** A login or password-reset endpoint that
30
+ responds differently for "user exists" vs "user doesn't" lets an
31
+ attacker harvest your customer list.
32
+ - **Bulk session invalidation.** When a user changes their password
33
+ because they think they were compromised, every existing token they
34
+ hold should stop working immediately. Most homegrown JWT layers
35
+ don't do this.
36
+ - **One-time tokens.** Verification and password-reset tokens should
37
+ be random, hashed at rest, single-use, and expire fast. Storing the
38
+ raw token in the database is a "now your DB backup is also a
39
+ credential dump" mistake.
40
+ - **Phone numbers.** SMS codes need E.164-validated numbers, attempt
41
+ limits, and an upstream provider. Wiring all of that yourself for a
42
+ single feature is rarely worth it.
43
+
44
+ Doing all of these correctly, with tests, is two to four weeks of
45
+ engineering for a competent team. Doing them once and embedding the
46
+ result everywhere is what regstack is for.
47
+
48
+ ## Why not just use…?
49
+
50
+ There are real alternatives. Here's why regstack might still be the
51
+ right call.
52
+
53
+ | Alternative | Why you might pick it | Why you might pick regstack instead |
54
+ |---|---|---|
55
+ | **[Auth0](https://auth0.com/) / [Clerk](https://clerk.com/) / [WorkOS](https://workos.com/) / [Stytch](https://stytch.com/)** (hosted SaaS) | Zero ops. Polished UI. Enterprise SSO out of the box. | Cost scales per-user. Your auth lives on someone else's servers. Your customer list is in their database. Vendor lock-in is real and migrations are painful. |
56
+ | **[Keycloak](https://www.keycloak.org/) / [Authentik](https://goauthentik.io/) / [Authelia](https://www.authelia.com/) / [Ory Kratos](https://www.ory.sh/kratos/)** (self-hosted IAM) | Full identity platform. SAML, OIDC, federation. | A separate Java/Go service to run, monitor, back up, upgrade, and reason about. Heavyweight for "let users sign up". Schema lives outside your app. |
57
+ | **[fastapi-users](https://fastapi-users.github.io/fastapi-users/)** | Same language, same framework. Good registration / login primitives. | Doesn't ship verification flows, anti-enumeration, bulk revoke, SMS MFA, admin endpoints, or themeable pages — you build those. regstack is the longer tail. |
58
+ | **Roll your own** | Total control. No dependency to learn. | You re-solve every bullet from "The problem" above, including the ones you didn't know existed yet. Two to four engineering weeks, then forever to maintain. |
59
+
60
+ regstack's bet is that for most FastAPI apps the right answer is
61
+ **embed a small Python library that owns the boring 80% correctly,
62
+ and keep the user table in your own database** — not "stand up a
63
+ separate auth product" and not "write the boring 80% from scratch
64
+ each time".
65
+
66
+ ## What's in the box
67
+
68
+ - **Three storage backends, one API.** SQLite (the default — single
69
+ file, no server), Postgres (via asyncpg), MongoDB (via pymongo).
70
+ Same routers, same hooks; switch by changing the `database_url`.
71
+ - **JSON API.** Register, verify email, resend verification, log in
72
+ (with optional SMS second step), log out, `me`, change password,
73
+ change email + confirm, forgot/reset password, delete account,
74
+ admin endpoints.
75
+ - **Server-rendered HTML pages** (opt-in). Login, register, verify,
76
+ forgot, reset, MFA confirm, account dashboard. Themed via CSS
77
+ custom properties — no template editing required for a re-skin.
78
+ Full template overrides are still possible per host.
79
+ - **CLIs.** `regstack init` (interactive setup wizard),
80
+ `regstack create-admin`, `regstack doctor`.
81
+ - **Pluggable email and SMS.** Email backends: `console` (dev), SMTP,
82
+ [Amazon SES](https://aws.amazon.com/ses/). SMS backends:
83
+ [Amazon SNS](https://aws.amazon.com/sns/),
84
+ [Twilio](https://www.twilio.com/). Plug your own in by implementing
85
+ one method.
86
+ - **Security defaults you would otherwise have to research.**
87
+ Argon2id password hashing, per-purpose
88
+ [JWT](https://datatracker.ietf.org/doc/html/rfc7519) signing keys,
89
+ per-token revocation, bulk session invalidation on password change,
90
+ login lockout with HTTP 429 + `Retry-After`, durable hashed
91
+ verification tokens, 6-digit SMS codes with attempt limits,
92
+ anti-enumeration on forgot/resend endpoints, CSP-friendly templates
93
+ with no inline styles.
94
+
95
+ ## Where to next
96
+
97
+ - New here? Start with the [Quickstart](quickstart.md) — install,
98
+ generate a config, register a user end-to-end.
99
+ - Embedding regstack in an existing app? Read
100
+ [Embedding](embedding.md) for the patterns most hosts adopt
101
+ (custom email, hooks, multiple regstacks, theming).
102
+ - Curious how it's put together internally? See
103
+ [Architecture](architecture.md).
104
+ - Designing a deployment? The [Security model](security.md) page is
105
+ the threat model, the JWT scheme, and the things you still own as
106
+ a host.
107
+
108
+ ```{toctree}
109
+ :maxdepth: 2
110
+ :caption: Getting started
111
+
112
+ quickstart
113
+ configuration
114
+ ```
115
+
116
+ ```{toctree}
117
+ :maxdepth: 2
118
+ :caption: Guides
119
+
120
+ architecture
121
+ security
122
+ embedding
123
+ theming
124
+ cli
125
+ ```
126
+
127
+ ```{toctree}
128
+ :maxdepth: 2
129
+ :caption: Reference
130
+
131
+ api
132
+ changelog
133
+ ```
134
+
135
+ ## Project status
136
+
137
+ Alpha. Latest tagged release: `v0.2.1`. SQLite is the default and
138
+ runs with no infrastructure; PostgreSQL and MongoDB pass the same
139
+ parametrized integration suite. The full backend matrix runs in
140
+ parallel against every test, so a green CI on `main` is a strong
141
+ correctness signal.
@@ -2,13 +2,13 @@
2
2
 
3
3
  This guide walks you from "nothing installed" to "registered user
4
4
  with a verified email" in about ten minutes. You will need
5
- [Python 3.11 or newer](https://www.python.org/downloads/) and
6
- [`uv`](https://docs.astral.sh/uv/) (Astral's fast Python package
7
- manager — used throughout regstack's tooling).
5
+ Python 3.11 or newer and [`uv`](https://docs.astral.sh/uv/)
6
+ (Astral's fast Python package manager — used throughout regstack's
7
+ tooling).
8
8
 
9
- The default storage backend is [SQLite](https://www.sqlite.org/index.html),
10
- so **no database server is required for development**. The same code
11
- runs against [PostgreSQL](https://www.postgresql.org/) or
9
+ The default storage backend is SQLite, so **no database server is
10
+ required for development**. The same code runs against
11
+ [PostgreSQL](https://www.postgresql.org/) or
12
12
  [MongoDB](https://www.mongodb.com/) by changing one URL.
13
13
 
14
14
  ## Install
@@ -45,9 +45,9 @@ The wizard asks a handful of questions and writes two files:
45
45
  1. Which backend do you want? (SQLite / Postgres / MongoDB)
46
46
  2. Builds the right `database_url` for your choice (a SQLite path, a
47
47
  Postgres connection URL, or a Mongo URL).
48
- 3. Generates a 64-byte JWT signing secret (used to sign and verify
49
- [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519) —
50
- keep this secret).
48
+ 3. Generates a 64-byte signing secret used to sign and verify
49
+ [JWTs](https://datatracker.ietf.org/doc/html/rfc7519) — keep this
50
+ secret.
51
51
  4. Walks through email backend (`console` / SMTP / SES) and feature
52
52
  flags.
53
53
 
@@ -131,8 +131,8 @@ if config.enable_ui_router:
131
131
  This adds pages at `/account/login`, `/account/register`,
132
132
  `/account/me`, etc., and serves the bundled `core.css`, `theme.css`,
133
133
  and `regstack.js` at `/regstack-static/`. The pages are stateless and
134
- talk to the JSON API via [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
135
- Re-skin them by serving a single CSS file — see [Theming](theming.md).
134
+ talk to the JSON API via `fetch`. Re-skin them by serving a single
135
+ CSS file — see [Theming](theming.md).
136
136
 
137
137
  ## End-to-end smoke test (zero infrastructure)
138
138