regstack 0.1.1__tar.gz → 0.2.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 (187) hide show
  1. {regstack-0.1.1 → regstack-0.2.0}/.github/workflows/publish.yml +4 -1
  2. {regstack-0.1.1 → regstack-0.2.0}/.github/workflows/test.yml +18 -3
  3. {regstack-0.1.1 → regstack-0.2.0}/CHANGELOG.md +12 -0
  4. {regstack-0.1.1 → regstack-0.2.0}/CLAUDE.md +24 -0
  5. regstack-0.2.0/PKG-INFO +272 -0
  6. regstack-0.2.0/README.md +208 -0
  7. regstack-0.2.0/docs/architecture.md +210 -0
  8. regstack-0.2.0/docs/changelog.md +149 -0
  9. {regstack-0.1.1 → regstack-0.2.0}/docs/cli.md +5 -4
  10. {regstack-0.1.1 → regstack-0.2.0}/docs/configuration.md +36 -5
  11. regstack-0.2.0/docs/embedding.md +200 -0
  12. regstack-0.2.0/docs/index.md +99 -0
  13. regstack-0.2.0/docs/quickstart.md +157 -0
  14. regstack-0.2.0/docs/security.md +226 -0
  15. regstack-0.2.0/examples/_common/app.py +101 -0
  16. regstack-0.2.0/examples/mongo/main.py +31 -0
  17. {regstack-0.1.1/examples/minimal → regstack-0.2.0/examples/mongo}/regstack.toml +1 -0
  18. regstack-0.2.0/examples/postgres/README.md +29 -0
  19. regstack-0.2.0/examples/postgres/main.py +30 -0
  20. regstack-0.2.0/examples/postgres/regstack.toml +29 -0
  21. regstack-0.2.0/examples/sqlite/README.md +33 -0
  22. regstack-0.2.0/examples/sqlite/main.py +34 -0
  23. regstack-0.2.0/examples/sqlite/regstack.toml +30 -0
  24. {regstack-0.1.1 → regstack-0.2.0}/pyproject.toml +16 -3
  25. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/app.py +31 -15
  26. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/dependencies.py +2 -2
  27. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/lockout.py +1 -1
  28. regstack-0.2.0/src/regstack/backends/__init__.py +21 -0
  29. regstack-0.2.0/src/regstack/backends/base.py +73 -0
  30. regstack-0.2.0/src/regstack/backends/factory.py +50 -0
  31. regstack-0.2.0/src/regstack/backends/mongo/__init__.py +5 -0
  32. regstack-0.2.0/src/regstack/backends/mongo/backend.py +57 -0
  33. regstack-0.2.0/src/regstack/backends/mongo/client.py +38 -0
  34. regstack-0.2.0/src/regstack/backends/mongo/repositories/__init__.py +27 -0
  35. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/mongo}/repositories/login_attempt_repo.py +6 -0
  36. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/mongo}/repositories/mfa_code_repo.py +14 -2
  37. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/mongo}/repositories/user_repo.py +18 -4
  38. regstack-0.2.0/src/regstack/backends/protocols.py +163 -0
  39. regstack-0.2.0/src/regstack/backends/sql/__init__.py +3 -0
  40. regstack-0.2.0/src/regstack/backends/sql/backend.py +73 -0
  41. regstack-0.2.0/src/regstack/backends/sql/migrations/__init__.py +124 -0
  42. regstack-0.2.0/src/regstack/backends/sql/migrations/env.py +69 -0
  43. regstack-0.2.0/src/regstack/backends/sql/migrations/script.py.mako +28 -0
  44. regstack-0.2.0/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +108 -0
  45. regstack-0.2.0/src/regstack/backends/sql/repositories/blacklist_repo.py +36 -0
  46. regstack-0.2.0/src/regstack/backends/sql/repositories/login_attempt_repo.py +45 -0
  47. regstack-0.2.0/src/regstack/backends/sql/repositories/mfa_code_repo.py +118 -0
  48. regstack-0.2.0/src/regstack/backends/sql/repositories/pending_repo.py +69 -0
  49. regstack-0.2.0/src/regstack/backends/sql/repositories/user_repo.py +198 -0
  50. regstack-0.2.0/src/regstack/backends/sql/schema.py +139 -0
  51. regstack-0.2.0/src/regstack/backends/sql/types.py +37 -0
  52. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/cli/__main__.py +3 -1
  53. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/cli/_runtime.py +7 -9
  54. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/cli/doctor.py +57 -33
  55. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/cli/init.py +46 -8
  56. regstack-0.2.0/src/regstack/cli/migrate.py +64 -0
  57. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/config/schema.py +7 -1
  58. regstack-0.2.0/src/regstack/models/_objectid.py +47 -0
  59. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/account.py +1 -1
  60. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/admin.py +10 -4
  61. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/login.py +1 -1
  62. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/phone.py +1 -1
  63. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/register.py +2 -2
  64. regstack-0.2.0/src/regstack/version.py +1 -0
  65. regstack-0.2.0/tasks.py +347 -0
  66. regstack-0.2.0/tests/conftest.py +266 -0
  67. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_indexes.py +14 -0
  68. regstack-0.2.0/tests/integration/test_sql_migrations.py +89 -0
  69. regstack-0.2.0/tests/unit/__init__.py +0 -0
  70. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_cli.py +13 -12
  71. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_lockout.py +20 -4
  72. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_mfa_code_repo.py +17 -4
  73. {regstack-0.1.1 → regstack-0.2.0}/uv.lock +219 -5
  74. regstack-0.1.1/PKG-INFO +0 -209
  75. regstack-0.1.1/README.md +0 -153
  76. regstack-0.1.1/docs/architecture.md +0 -134
  77. regstack-0.1.1/docs/changelog.md +0 -82
  78. regstack-0.1.1/docs/embedding.md +0 -146
  79. regstack-0.1.1/docs/index.md +0 -55
  80. regstack-0.1.1/docs/quickstart.md +0 -115
  81. regstack-0.1.1/docs/security.md +0 -170
  82. regstack-0.1.1/examples/minimal/main.py +0 -109
  83. regstack-0.1.1/src/regstack/db/__init__.py +0 -17
  84. regstack-0.1.1/src/regstack/db/client.py +0 -26
  85. regstack-0.1.1/src/regstack/models/_objectid.py +0 -30
  86. regstack-0.1.1/src/regstack/version.py +0 -1
  87. regstack-0.1.1/tasks.py +0 -98
  88. regstack-0.1.1/tests/conftest.py +0 -144
  89. {regstack-0.1.1 → regstack-0.2.0}/.gitignore +0 -0
  90. {regstack-0.1.1 → regstack-0.2.0}/.python-version +0 -0
  91. {regstack-0.1.1 → regstack-0.2.0}/.readthedocs.yaml +0 -0
  92. {regstack-0.1.1 → regstack-0.2.0}/LICENSE +0 -0
  93. {regstack-0.1.1 → regstack-0.2.0}/NOTICE +0 -0
  94. {regstack-0.1.1 → regstack-0.2.0}/SECURITY.md +0 -0
  95. {regstack-0.1.1 → regstack-0.2.0}/docs/_static/.gitkeep +0 -0
  96. {regstack-0.1.1 → regstack-0.2.0}/docs/_templates/.gitkeep +0 -0
  97. {regstack-0.1.1 → regstack-0.2.0}/docs/api.md +0 -0
  98. {regstack-0.1.1 → regstack-0.2.0}/docs/conf.py +0 -0
  99. {regstack-0.1.1 → regstack-0.2.0}/docs/theming.md +0 -0
  100. {regstack-0.1.1/src/regstack/cli → regstack-0.2.0/examples/_common}/__init__.py +0 -0
  101. {regstack-0.1.1/examples/minimal → regstack-0.2.0/examples/mongo}/README.md +0 -0
  102. {regstack-0.1.1/examples/minimal → regstack-0.2.0/examples/mongo}/branding/theme.css +0 -0
  103. {regstack-0.1.1 → regstack-0.2.0}/regstack.toml.example +0 -0
  104. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/__init__.py +0 -0
  105. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/__init__.py +0 -0
  106. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/clock.py +0 -0
  107. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/jwt.py +0 -0
  108. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/mfa.py +0 -0
  109. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/password.py +0 -0
  110. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/auth/tokens.py +0 -0
  111. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/mongo}/indexes.py +0 -0
  112. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/mongo}/repositories/blacklist_repo.py +0 -0
  113. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/mongo}/repositories/pending_repo.py +0 -0
  114. {regstack-0.1.1/src/regstack/db → regstack-0.2.0/src/regstack/backends/sql}/repositories/__init__.py +0 -0
  115. {regstack-0.1.1/tests → regstack-0.2.0/src/regstack/cli}/__init__.py +0 -0
  116. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/cli/admin.py +0 -0
  117. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/config/__init__.py +0 -0
  118. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/config/loader.py +0 -0
  119. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/config/secrets.py +0 -0
  120. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/__init__.py +0 -0
  121. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/base.py +0 -0
  122. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/composer.py +0 -0
  123. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/console.py +0 -0
  124. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/factory.py +0 -0
  125. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/ses.py +0 -0
  126. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/smtp.py +0 -0
  127. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/email_change.html +0 -0
  128. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/email_change.subject.txt +0 -0
  129. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/email_change.txt +0 -0
  130. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/password_reset.html +0 -0
  131. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/password_reset.subject.txt +0 -0
  132. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/password_reset.txt +0 -0
  133. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
  134. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
  135. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/verification.html +0 -0
  136. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/verification.subject.txt +0 -0
  137. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/email/templates/verification.txt +0 -0
  138. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/hooks/__init__.py +0 -0
  139. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/hooks/events.py +0 -0
  140. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/models/__init__.py +0 -0
  141. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/models/login_attempt.py +0 -0
  142. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/models/mfa_code.py +0 -0
  143. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/models/pending_registration.py +0 -0
  144. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/models/user.py +0 -0
  145. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/__init__.py +0 -0
  146. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/_schemas.py +0 -0
  147. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/logout.py +0 -0
  148. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/password.py +0 -0
  149. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/routers/verify.py +0 -0
  150. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/sms/__init__.py +0 -0
  151. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/sms/base.py +0 -0
  152. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/sms/factory.py +0 -0
  153. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/sms/null.py +0 -0
  154. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/sms/sns.py +0 -0
  155. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/sms/twilio.py +0 -0
  156. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/__init__.py +0 -0
  157. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/pages.py +0 -0
  158. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/static/css/core.css +0 -0
  159. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/static/css/theme.css +0 -0
  160. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/static/js/regstack.js +0 -0
  161. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
  162. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/forgot.html +0 -0
  163. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/login.html +0 -0
  164. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/me.html +0 -0
  165. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
  166. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/register.html +0 -0
  167. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/reset.html +0 -0
  168. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/auth/verify.html +0 -0
  169. {regstack-0.1.1 → regstack-0.2.0}/src/regstack/ui/templates/base.html +0 -0
  170. {regstack-0.1.1/tests/integration → regstack-0.2.0/tests}/__init__.py +0 -0
  171. {regstack-0.1.1/tests/unit → regstack-0.2.0/tests/integration}/__init__.py +0 -0
  172. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_account_management.py +0 -0
  173. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_admin_router.py +0 -0
  174. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_happy_path.py +0 -0
  175. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_login_lockout.py +0 -0
  176. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_mfa.py +0 -0
  177. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_password_reset.py +0 -0
  178. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_ui_router.py +0 -0
  179. {regstack-0.1.1 → regstack-0.2.0}/tests/integration/test_verification.py +0 -0
  180. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_config_loader.py +0 -0
  181. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_jwt.py +0 -0
  182. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_mail_composer.py +0 -0
  183. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_password.py +0 -0
  184. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_ses_backend.py +0 -0
  185. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_sms.py +0 -0
  186. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_smtp_backend.py +0 -0
  187. {regstack-0.1.1 → regstack-0.2.0}/tests/unit/test_ui_env.py +0 -0
@@ -30,7 +30,10 @@ jobs:
30
30
  run: |
31
31
  tag="${GITHUB_REF##*/}" # e.g. v0.1.0
32
32
  tag_version="${tag#v}" # e.g. 0.1.0
33
- pkg_version=$(uv run python -c 'from regstack.version import __version__; print(__version__)')
33
+ # Read version from pyproject.toml directly importing the package
34
+ # would pull in optional-extra dependencies (e.g. pymongo) that
35
+ # aren't installed in this build job.
36
+ pkg_version=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
34
37
  if [ "$tag_version" != "$pkg_version" ]; then
35
38
  echo "Tag $tag does not match package version $pkg_version" >&2
36
39
  exit 1
@@ -22,15 +22,30 @@ jobs:
22
22
  image: mongo:7
23
23
  ports:
24
24
  - 27017:27017
25
- # Wait until mongo answers a `ping` before the tests start.
26
25
  options: >-
27
26
  --health-cmd "mongosh --quiet --eval 'db.runCommand({ping:1}).ok' --port 27017"
28
27
  --health-interval 5s
29
28
  --health-timeout 5s
30
29
  --health-retries 12
31
30
  --health-start-period 10s
31
+ postgres:
32
+ image: postgres:16
33
+ ports:
34
+ - 5432:5432
35
+ env:
36
+ POSTGRES_USER: regstack
37
+ POSTGRES_PASSWORD: regstack
38
+ POSTGRES_DB: regstack
39
+ options: >-
40
+ --health-cmd "pg_isready -U regstack"
41
+ --health-interval 5s
42
+ --health-timeout 5s
43
+ --health-retries 12
44
+ --health-start-period 10s
32
45
  env:
33
- REGSTACK_MONGODB_URL: mongodb://localhost:27017
46
+ # Drives the parametrized backend fixture in tests/conftest.py.
47
+ # Connect as superuser so the per-test fixture can CREATE/DROP DATABASE.
48
+ REGSTACK_TEST_POSTGRES_URL: postgresql+asyncpg://regstack:regstack@localhost:5432
34
49
  steps:
35
50
  - uses: actions/checkout@v4
36
51
 
@@ -51,7 +66,7 @@ jobs:
51
66
  - name: ruff format check
52
67
  run: uv run ruff format --check src tests
53
68
 
54
- - name: pytest (parallel)
69
+ - name: pytest (parallel, all backends)
55
70
  run: uv run python -m pytest -n auto --tb=short
56
71
 
57
72
  docs:
@@ -5,6 +5,18 @@ 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.0 — 2026-04-28
9
+
10
+ Multi-backend support — SQLite (default), Postgres, MongoDB — switched
11
+ by `database_url` URL scheme. Bundled Alembic migrations for SQL
12
+ backends. Embedding API change: `RegStack(config=, db=)` →
13
+ `RegStack(config=, backend=None)`. README + core docs rewritten for
14
+ less-expert readers (problem framing, hyperlinks to external
15
+ standards, comparison vs Auth0/Clerk/Keycloak/fastapi-users).
16
+
17
+ See [`docs/changelog.md`](docs/changelog.md) for the full per-feature
18
+ breakdown.
19
+
8
20
  ## 0.1.1 — 2026-04-27
9
21
 
10
22
  - Rewrite README relative links as absolute URLs so they resolve on the
@@ -287,6 +287,30 @@ collections.** The full suite must pass under `pytest -n auto` reliably —
287
287
  flaky tests are bugs, not noise. Time-dependent assertions use the
288
288
  `frozen_clock` fixture, never `time.sleep` or wall-clock delays.
289
289
 
290
+ ### A test run that doesn't cover every backend is a failing test
291
+
292
+ The whole point of the SQL + Mongo abstraction is parity. **Reporting
293
+ "all tests passed" when the run only exercised one or two backends is a
294
+ lie.** Treat any partial-backend run as red until it's been re-run
295
+ across the full matrix.
296
+
297
+ Concretely:
298
+
299
+ - `inv test-sqlite` is fine for tight inner-loop iteration, but it is
300
+ **not** a release gate.
301
+ - Before declaring a feature done — and definitely before merging /
302
+ tagging — run `inv test-all`, which exercises sqlite + mongo +
303
+ postgres in one go. Use `inv db-up` first to bring the local services
304
+ up if they aren't already (`inv db-status` reports what's running).
305
+ - The CI matrix runs all three backends on every push. A green local
306
+ `inv test-sqlite` followed by a CI failure on `mongo` or `postgres`
307
+ is the failure pattern this rule exists to prevent.
308
+ - A test that's `mongo`-only or `sqlite`-only by design must
309
+ short-circuit the parametrized fixture (see how
310
+ `tests/integration/test_indexes.py` does it with a local
311
+ `backend_kind = "mongo"` fixture override). Skipping silently because
312
+ a service isn't running doesn't count.
313
+
290
314
  ## Conventions
291
315
 
292
316
  - **Python 3.11+.** Use `from __future__ import annotations` in every module.
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: regstack
3
+ Version: 0.2.0
4
+ Summary: Embeddable user registration, login, and account management for FastAPI apps. SQLite / Postgres / MongoDB.
5
+ Project-URL: Homepage, https://github.com/jdrumgoole/regstack
6
+ Project-URL: Repository, https://github.com/jdrumgoole/regstack
7
+ Author-email: Joe Drumgoole <joe@joedrumgoole.com>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ License-File: NOTICE
11
+ Keywords: auth,fastapi,mongodb,registration,users
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: aiosmtplib>=3.0
20
+ Requires-Dist: aiosqlite>=0.20
21
+ Requires-Dist: alembic>=1.13
22
+ Requires-Dist: click>=8.1
23
+ Requires-Dist: dnspython>=2.6
24
+ Requires-Dist: email-validator>=2.1
25
+ Requires-Dist: fastapi>=0.110
26
+ Requires-Dist: jinja2>=3.1
27
+ Requires-Dist: pwdlib[argon2]>=0.2.1
28
+ Requires-Dist: pydantic-settings>=2.2
29
+ Requires-Dist: pydantic>=2.6
30
+ Requires-Dist: pyjwt>=2.8
31
+ Requires-Dist: python-multipart>=0.0.9
32
+ Requires-Dist: sqlalchemy[asyncio]>=2.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: anyio>=4.3; extra == 'dev'
35
+ Requires-Dist: asyncpg>=0.29; extra == 'dev'
36
+ Requires-Dist: httpx>=0.27; extra == 'dev'
37
+ Requires-Dist: invoke>=2.2; extra == 'dev'
38
+ Requires-Dist: mypy>=1.10; extra == 'dev'
39
+ Requires-Dist: pymongo>=4.9; extra == 'dev'
40
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
41
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
42
+ Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
43
+ Requires-Dist: pytest>=8.0; extra == 'dev'
44
+ Requires-Dist: ruff>=0.4; extra == 'dev'
45
+ Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
46
+ Provides-Extra: docs
47
+ Requires-Dist: furo>=2024.1; extra == 'docs'
48
+ Requires-Dist: myst-parser>=3.0; extra == 'docs'
49
+ Requires-Dist: sphinx-autobuild>=2024.4; extra == 'docs'
50
+ Requires-Dist: sphinx-autodoc-typehints>=2.0; extra == 'docs'
51
+ Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
52
+ Requires-Dist: sphinx>=7.3; extra == 'docs'
53
+ Provides-Extra: mongo
54
+ Requires-Dist: pymongo>=4.9; extra == 'mongo'
55
+ Provides-Extra: postgres
56
+ Requires-Dist: asyncpg>=0.29; extra == 'postgres'
57
+ Provides-Extra: ses
58
+ Requires-Dist: aioboto3>=12.3; extra == 'ses'
59
+ Provides-Extra: sns
60
+ Requires-Dist: aioboto3>=12.3; extra == 'sns'
61
+ Provides-Extra: twilio
62
+ Requires-Dist: twilio>=9.0; extra == 'twilio'
63
+ Description-Content-Type: text/markdown
64
+
65
+ # regstack
66
+
67
+ [![CI](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml/badge.svg)](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml)
68
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
69
+ [![FastAPI](https://img.shields.io/badge/built%20with-FastAPI-009688.svg)](https://fastapi.tiangolo.com/)
70
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](https://github.com/jdrumgoole/regstack/blob/main/LICENSE)
71
+
72
+ **Production-grade user accounts for your [FastAPI](https://fastapi.tiangolo.com/)
73
+ app — without the vendor lock-in, the second service to run, or the homegrown
74
+ auth bugs.**
75
+
76
+ `pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
77
+ or [MongoDB](https://www.mongodb.com/), and you have register / login /
78
+ verify-email / reset-password / change-email / delete-account / optional SMS
79
+ two-factor / admin endpoints / themable HTML pages — all behind a small Python
80
+ API and one config file.
81
+
82
+ 📚 **Docs:** <https://regstack.readthedocs.io>
83
+ &nbsp;·&nbsp;
84
+ 🧪 **Try it:** [`examples/sqlite`](https://github.com/jdrumgoole/regstack/tree/main/examples/sqlite)
85
+ &nbsp;·&nbsp;
86
+ 🛡️ **Security model:** [security guide](https://regstack.readthedocs.io/en/latest/security.html)
87
+
88
+ ---
89
+
90
+ ## The problem regstack solves
91
+
92
+ Every web application that has users eventually needs the same dozen
93
+ endpoints: register, log in, log out, verify email, reset a forgotten
94
+ password, change password, change email, delete account, list users for
95
+ the admin panel, lock out brute-force attackers, and ideally a second
96
+ factor. Every one of those endpoints has a well-known way to get
97
+ subtly wrong:
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.
102
+ - **Token revocation.** A [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
103
+ is signed and self-contained: the server can't "log it out" unless you
104
+ build a revocation list. Forget this and a stolen token works until it
105
+ expires.
106
+ - **Account enumeration.** A login or password-reset endpoint that
107
+ 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).
110
+ - **Bulk session invalidation.** When a user changes their password
111
+ because they think they were compromised, every existing token they
112
+ hold should stop working immediately. Most homegrown JWT layers don't
113
+ do this.
114
+ - **One-time tokens.** Verification and password-reset tokens should be
115
+ random, hashed at rest, single-use, and expire fast. Storing the raw
116
+ token in the database is a "now your DB backup is also a credential
117
+ 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.
121
+
122
+ Doing all of these correctly, with tests, is two to four weeks of
123
+ engineering for a competent team. Doing them once and embedding the
124
+ result everywhere is what regstack is for.
125
+
126
+ ## What you get
127
+
128
+ ```
129
+ ✔ Email + password registration with email verification
130
+ ✔ JWT login (RFC 7519) with per-token revoke AND bulk revoke
131
+ ✔ Forgot / reset password — anti-enumeration: identical responses
132
+ ✔ Change password (revokes old tokens) / change email (re-verify)
133
+ ✔ Delete account
134
+ ✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
135
+ ✔ Server-side login lockout (HTTP 429 + Retry-After)
136
+ ✔ Admin endpoints (list / disable / delete users, stats)
137
+ ✔ Server-rendered HTML pages, theme with one CSS file
138
+ ✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
139
+ ✔ Argon2 password hashing, CSP-friendly templates
140
+ ✔ Setup wizard (`regstack init`) and config validator (`regstack doctor`)
141
+ ✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
142
+ ```
143
+
144
+ Every feature is opt-in. Mount only the JSON router for a headless
145
+ backend; flip `enable_ui_router` to also get the bundled SSR pages.
146
+ Skip the SMS extras and you don't pull `twilio` or `aioboto3`.
147
+
148
+ ## Why not just use…?
149
+
150
+ There are real alternatives. Here's why regstack might still be the
151
+ right call.
152
+
153
+ | Alternative | Why you might pick it | Why you might pick regstack instead |
154
+ |---|---|---|
155
+ | **[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. |
156
+ | **[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. |
157
+ | **[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 themable pages — you build those. regstack is the longer tail. |
158
+ | **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. |
159
+
160
+ regstack's bet is that for most FastAPI apps the right answer is
161
+ **embed a small Python library that owns the boring 80% correctly, and
162
+ keep the user table in your own database** — not "stand up a separate
163
+ auth product" and not "write the boring 80% from scratch each time".
164
+
165
+ ## 30-second start
166
+
167
+ ```bash
168
+ git clone https://github.com/jdrumgoole/regstack && cd regstack
169
+ uv sync --extra dev
170
+
171
+ # Generate a JWT signing secret. SQLite is the default backend, no DB to install.
172
+ export REGSTACK_JWT_SECRET=$(python -c 'import secrets; print(secrets.token_urlsafe(64))')
173
+
174
+ uv run uvicorn examples.sqlite.main:app --reload
175
+ ```
176
+
177
+ Then visit <http://localhost:8000/account/login> in your browser, or
178
+ register from the command line:
179
+
180
+ ```bash
181
+ curl -X POST http://localhost:8000/api/auth/register \
182
+ -H 'content-type: application/json' \
183
+ -d '{"email":"alice@example.com","password":"hunter2hunter2","full_name":"Alice"}'
184
+ ```
185
+
186
+ The bundled example serves themed SSR pages at `/account/*`, prints
187
+ verification / reset links and SMS codes to stdout (the `console`
188
+ email/SMS backends), and shows how a host overrides regstack's default
189
+ look by serving its own `theme.css`.
190
+
191
+ Want PostgreSQL or MongoDB instead? Set `REGSTACK_DATABASE_URL` to a
192
+ `postgresql+asyncpg://...` or `mongodb://...` URL and install the matching
193
+ extra (`uv sync --extra postgres` or `uv sync --extra mongo`). The
194
+ schema is created on first boot.
195
+
196
+ ## Embed in your own app
197
+
198
+ ```python
199
+ from contextlib import asynccontextmanager
200
+
201
+ from fastapi import FastAPI
202
+
203
+ from regstack import RegStack, RegStackConfig
204
+
205
+
206
+ config = RegStackConfig.load() # env vars + regstack.toml
207
+ regstack = RegStack(config=config)
208
+
209
+
210
+ @asynccontextmanager
211
+ async def lifespan(app: FastAPI):
212
+ await regstack.install_schema() # idempotent: runs migrations / creates indexes
213
+ yield
214
+ await regstack.aclose()
215
+
216
+
217
+ app = FastAPI(lifespan=lifespan)
218
+ app.include_router(regstack.router, prefix=config.api_prefix)
219
+ app.include_router(regstack.ui_router, prefix=config.ui_prefix) # optional
220
+ app.mount(config.static_prefix, regstack.static_files) # optional
221
+ ```
222
+
223
+ That is the whole integration. The rest of the surface area —
224
+ extending the user model, registering hooks (`user_registered`,
225
+ `password_reset`, …), supplying your own email service — is in the
226
+ [embedding guide](https://regstack.readthedocs.io/en/latest/embedding.html).
227
+
228
+ ## Documentation
229
+
230
+ | Page | What's there |
231
+ |---|---|
232
+ | [Quickstart](https://regstack.readthedocs.io/en/latest/quickstart.html) | Install, wizard, minimal embed |
233
+ | [Configuration](https://regstack.readthedocs.io/en/latest/configuration.html) | Every `RegStackConfig` field, env vars, TOML layout |
234
+ | [Architecture](https://regstack.readthedocs.io/en/latest/architecture.html) | Façade, backends, repos, hooks, lifecycle |
235
+ | [Security model](https://regstack.readthedocs.io/en/latest/security.html) | Threat model, JWT scheme, anti-enumeration, MFA |
236
+ | [Embedding](https://regstack.readthedocs.io/en/latest/embedding.html) | Custom backends, hooks, multi-tenant |
237
+ | [Theming](https://regstack.readthedocs.io/en/latest/theming.html) | CSS variables, template overrides |
238
+ | [CLI](https://regstack.readthedocs.io/en/latest/cli.html) | `init`, `create-admin`, `doctor` |
239
+ | [API reference](https://regstack.readthedocs.io/en/latest/api.html) | Public types, generated from source |
240
+
241
+ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdrumgoole/regstack/tree/main/docs).
242
+
243
+ ## Status
244
+
245
+ Alpha. Single-file SQLite is the default and runs with no infrastructure;
246
+ PostgreSQL and MongoDB backends pass the same parametrized integration
247
+ suite. The next tagged release is `v0.2.0`. See the
248
+ [changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
249
+ for the per-milestone breakdown.
250
+
251
+ ## Contributing
252
+
253
+ Issues and pull requests welcome at
254
+ <https://github.com/jdrumgoole/regstack>. Before opening a PR, please
255
+ run the test suite and the linter — both should be green:
256
+
257
+ ```bash
258
+ uv sync --extra dev
259
+ uv run python -m invoke test-all # SQLite + Mongo + Postgres in parallel
260
+ uv run python -m invoke lint # ruff + format check + mypy
261
+ ```
262
+
263
+ `invoke test-sqlite` is the fast inner-loop variant that needs no
264
+ database services. `invoke test-all` is what CI runs and what gates a
265
+ release. Each pytest-xdist worker isolates its own database, so the
266
+ full suite is safe to re-run while you iterate.
267
+
268
+ Security disclosures: see [SECURITY.md](https://github.com/jdrumgoole/regstack/blob/main/SECURITY.md).
269
+
270
+ ## License
271
+
272
+ [Apache License 2.0](https://github.com/jdrumgoole/regstack/blob/main/LICENSE) © 2026 Joe Drumgoole.
@@ -0,0 +1,208 @@
1
+ # regstack
2
+
3
+ [![CI](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml/badge.svg)](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
5
+ [![FastAPI](https://img.shields.io/badge/built%20with-FastAPI-009688.svg)](https://fastapi.tiangolo.com/)
6
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](https://github.com/jdrumgoole/regstack/blob/main/LICENSE)
7
+
8
+ **Production-grade user accounts for your [FastAPI](https://fastapi.tiangolo.com/)
9
+ app — without the vendor lock-in, the second service to run, or the homegrown
10
+ auth bugs.**
11
+
12
+ `pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
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.
17
+
18
+ 📚 **Docs:** <https://regstack.readthedocs.io>
19
+ &nbsp;·&nbsp;
20
+ 🧪 **Try it:** [`examples/sqlite`](https://github.com/jdrumgoole/regstack/tree/main/examples/sqlite)
21
+ &nbsp;·&nbsp;
22
+ 🛡️ **Security model:** [security guide](https://regstack.readthedocs.io/en/latest/security.html)
23
+
24
+ ---
25
+
26
+ ## The problem regstack solves
27
+
28
+ Every web application that has users eventually needs the same dozen
29
+ endpoints: register, log in, log out, verify email, reset a forgotten
30
+ password, change password, change email, delete account, list users for
31
+ the admin panel, lock out brute-force attackers, and ideally a second
32
+ factor. Every one of those endpoints has a well-known way to get
33
+ subtly wrong:
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.
38
+ - **Token revocation.** A [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
39
+ is signed and self-contained: the server can't "log it out" unless you
40
+ build a revocation list. Forget this and a stolen token works until it
41
+ expires.
42
+ - **Account enumeration.** A login or password-reset endpoint that
43
+ 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).
46
+ - **Bulk session invalidation.** When a user changes their password
47
+ because they think they were compromised, every existing token they
48
+ hold should stop working immediately. Most homegrown JWT layers don't
49
+ do this.
50
+ - **One-time tokens.** Verification and password-reset tokens should be
51
+ random, hashed at rest, single-use, and expire fast. Storing the raw
52
+ token in the database is a "now your DB backup is also a credential
53
+ 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.
57
+
58
+ Doing all of these correctly, with tests, is two to four weeks of
59
+ engineering for a competent team. Doing them once and embedding the
60
+ result everywhere is what regstack is for.
61
+
62
+ ## What you get
63
+
64
+ ```
65
+ ✔ Email + password registration with email verification
66
+ ✔ JWT login (RFC 7519) with per-token revoke AND bulk revoke
67
+ ✔ Forgot / reset password — anti-enumeration: identical responses
68
+ ✔ Change password (revokes old tokens) / change email (re-verify)
69
+ ✔ Delete account
70
+ ✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
71
+ ✔ Server-side login lockout (HTTP 429 + Retry-After)
72
+ ✔ Admin endpoints (list / disable / delete users, stats)
73
+ ✔ Server-rendered HTML pages, theme with one CSS file
74
+ ✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
75
+ ✔ Argon2 password hashing, CSP-friendly templates
76
+ ✔ Setup wizard (`regstack init`) and config validator (`regstack doctor`)
77
+ ✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
78
+ ```
79
+
80
+ Every feature is opt-in. Mount only the JSON router for a headless
81
+ backend; flip `enable_ui_router` to also get the bundled SSR pages.
82
+ Skip the SMS extras and you don't pull `twilio` or `aioboto3`.
83
+
84
+ ## Why not just use…?
85
+
86
+ There are real alternatives. Here's why regstack might still be the
87
+ right call.
88
+
89
+ | Alternative | Why you might pick it | Why you might pick regstack instead |
90
+ |---|---|---|
91
+ | **[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. |
92
+ | **[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. |
93
+ | **[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 themable pages — you build those. regstack is the longer tail. |
94
+ | **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. |
95
+
96
+ regstack's bet is that for most FastAPI apps the right answer is
97
+ **embed a small Python library that owns the boring 80% correctly, and
98
+ keep the user table in your own database** — not "stand up a separate
99
+ auth product" and not "write the boring 80% from scratch each time".
100
+
101
+ ## 30-second start
102
+
103
+ ```bash
104
+ git clone https://github.com/jdrumgoole/regstack && cd regstack
105
+ uv sync --extra dev
106
+
107
+ # Generate a JWT signing secret. SQLite is the default backend, no DB to install.
108
+ export REGSTACK_JWT_SECRET=$(python -c 'import secrets; print(secrets.token_urlsafe(64))')
109
+
110
+ uv run uvicorn examples.sqlite.main:app --reload
111
+ ```
112
+
113
+ Then visit <http://localhost:8000/account/login> in your browser, or
114
+ register from the command line:
115
+
116
+ ```bash
117
+ curl -X POST http://localhost:8000/api/auth/register \
118
+ -H 'content-type: application/json' \
119
+ -d '{"email":"alice@example.com","password":"hunter2hunter2","full_name":"Alice"}'
120
+ ```
121
+
122
+ The bundled example serves themed SSR pages at `/account/*`, prints
123
+ verification / reset links and SMS codes to stdout (the `console`
124
+ email/SMS backends), and shows how a host overrides regstack's default
125
+ look by serving its own `theme.css`.
126
+
127
+ Want PostgreSQL or MongoDB instead? Set `REGSTACK_DATABASE_URL` to a
128
+ `postgresql+asyncpg://...` or `mongodb://...` URL and install the matching
129
+ extra (`uv sync --extra postgres` or `uv sync --extra mongo`). The
130
+ schema is created on first boot.
131
+
132
+ ## Embed in your own app
133
+
134
+ ```python
135
+ from contextlib import asynccontextmanager
136
+
137
+ from fastapi import FastAPI
138
+
139
+ from regstack import RegStack, RegStackConfig
140
+
141
+
142
+ config = RegStackConfig.load() # env vars + regstack.toml
143
+ regstack = RegStack(config=config)
144
+
145
+
146
+ @asynccontextmanager
147
+ async def lifespan(app: FastAPI):
148
+ await regstack.install_schema() # idempotent: runs migrations / creates indexes
149
+ yield
150
+ await regstack.aclose()
151
+
152
+
153
+ app = FastAPI(lifespan=lifespan)
154
+ app.include_router(regstack.router, prefix=config.api_prefix)
155
+ app.include_router(regstack.ui_router, prefix=config.ui_prefix) # optional
156
+ app.mount(config.static_prefix, regstack.static_files) # optional
157
+ ```
158
+
159
+ That is the whole integration. The rest of the surface area —
160
+ extending the user model, registering hooks (`user_registered`,
161
+ `password_reset`, …), supplying your own email service — is in the
162
+ [embedding guide](https://regstack.readthedocs.io/en/latest/embedding.html).
163
+
164
+ ## Documentation
165
+
166
+ | Page | What's there |
167
+ |---|---|
168
+ | [Quickstart](https://regstack.readthedocs.io/en/latest/quickstart.html) | Install, wizard, minimal embed |
169
+ | [Configuration](https://regstack.readthedocs.io/en/latest/configuration.html) | Every `RegStackConfig` field, env vars, TOML layout |
170
+ | [Architecture](https://regstack.readthedocs.io/en/latest/architecture.html) | Façade, backends, repos, hooks, lifecycle |
171
+ | [Security model](https://regstack.readthedocs.io/en/latest/security.html) | Threat model, JWT scheme, anti-enumeration, MFA |
172
+ | [Embedding](https://regstack.readthedocs.io/en/latest/embedding.html) | Custom backends, hooks, multi-tenant |
173
+ | [Theming](https://regstack.readthedocs.io/en/latest/theming.html) | CSS variables, template overrides |
174
+ | [CLI](https://regstack.readthedocs.io/en/latest/cli.html) | `init`, `create-admin`, `doctor` |
175
+ | [API reference](https://regstack.readthedocs.io/en/latest/api.html) | Public types, generated from source |
176
+
177
+ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdrumgoole/regstack/tree/main/docs).
178
+
179
+ ## Status
180
+
181
+ Alpha. Single-file SQLite is the default and runs with no infrastructure;
182
+ PostgreSQL and MongoDB backends pass the same parametrized integration
183
+ suite. The next tagged release is `v0.2.0`. See the
184
+ [changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
185
+ for the per-milestone breakdown.
186
+
187
+ ## Contributing
188
+
189
+ Issues and pull requests welcome at
190
+ <https://github.com/jdrumgoole/regstack>. Before opening a PR, please
191
+ run the test suite and the linter — both should be green:
192
+
193
+ ```bash
194
+ uv sync --extra dev
195
+ uv run python -m invoke test-all # SQLite + Mongo + Postgres in parallel
196
+ uv run python -m invoke lint # ruff + format check + mypy
197
+ ```
198
+
199
+ `invoke test-sqlite` is the fast inner-loop variant that needs no
200
+ database services. `invoke test-all` is what CI runs and what gates a
201
+ release. Each pytest-xdist worker isolates its own database, so the
202
+ full suite is safe to re-run while you iterate.
203
+
204
+ Security disclosures: see [SECURITY.md](https://github.com/jdrumgoole/regstack/blob/main/SECURITY.md).
205
+
206
+ ## License
207
+
208
+ [Apache License 2.0](https://github.com/jdrumgoole/regstack/blob/main/LICENSE) © 2026 Joe Drumgoole.