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.
- {regstack-0.2.1 → regstack-0.2.3}/CHANGELOG.md +20 -0
- {regstack-0.2.1 → regstack-0.2.3}/PKG-INFO +9 -11
- {regstack-0.2.1 → regstack-0.2.3}/README.md +8 -10
- regstack-0.2.3/docs/api.md +278 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/architecture.md +35 -44
- {regstack-0.2.1 → regstack-0.2.3}/docs/changelog.md +53 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/embedding.md +10 -13
- regstack-0.2.3/docs/index.md +141 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/quickstart.md +11 -11
- {regstack-0.2.1 → regstack-0.2.3}/docs/security.md +49 -68
- {regstack-0.2.1 → regstack-0.2.3}/pyproject.toml +1 -1
- regstack-0.2.3/src/regstack/app.py +330 -0
- regstack-0.2.3/src/regstack/auth/clock.py +73 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/dependencies.py +50 -9
- regstack-0.2.3/src/regstack/auth/jwt.py +256 -0
- regstack-0.2.3/src/regstack/auth/lockout.py +119 -0
- regstack-0.2.3/src/regstack/auth/password.py +65 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/protocols.py +31 -6
- regstack-0.2.3/src/regstack/email/base.py +71 -0
- regstack-0.2.3/src/regstack/hooks/events.py +98 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/__init__.py +21 -1
- regstack-0.2.3/src/regstack/sms/base.py +67 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/pages.py +49 -8
- regstack-0.2.3/src/regstack/version.py +1 -0
- {regstack-0.2.1 → regstack-0.2.3}/uv.lock +1 -1
- regstack-0.2.1/docs/api.md +0 -184
- regstack-0.2.1/docs/index.md +0 -99
- regstack-0.2.1/src/regstack/app.py +0 -166
- regstack-0.2.1/src/regstack/auth/clock.py +0 -29
- regstack-0.2.1/src/regstack/auth/jwt.py +0 -145
- regstack-0.2.1/src/regstack/auth/lockout.py +0 -59
- regstack-0.2.1/src/regstack/auth/password.py +0 -20
- regstack-0.2.1/src/regstack/email/base.py +0 -23
- regstack-0.2.1/src/regstack/hooks/events.py +0 -59
- regstack-0.2.1/src/regstack/sms/base.py +0 -24
- regstack-0.2.1/src/regstack/version.py +0 -1
- {regstack-0.2.1 → regstack-0.2.3}/.github/workflows/publish.yml +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/.github/workflows/test.yml +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/.gitignore +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/.python-version +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/.readthedocs.yaml +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/CLAUDE.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/LICENSE +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/NOTICE +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/SECURITY.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/_static/.gitkeep +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/_templates/.gitkeep +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/cli.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/conf.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/configuration.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/docs/theming.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/_common/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/_common/app.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/README.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/branding/theme.css +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/main.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/mongo/regstack.toml +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/postgres/README.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/postgres/main.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/postgres/regstack.toml +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/sqlite/README.md +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/sqlite/main.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/examples/sqlite/regstack.toml +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/regstack.toml.example +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/base.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/factory.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/backend.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/client.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/indexes.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/env.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/backends/sql/types.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/__main__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/admin.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/doctor.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/init.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/loader.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/schema.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/config/secrets.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/composer.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/console.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/factory.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/ses.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/smtp.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/_objectid.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/models/user.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/account.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/admin.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/login.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/logout.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/password.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/phone.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/register.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/routers/verify.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/factory.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/null.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/sns.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/static/js/regstack.js +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tasks.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/conftest.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_account_management.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_admin_router.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_happy_path.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_indexes.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_mfa.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_sql_migrations.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/integration/test_verification.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/__init__.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_base_install_imports.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_cli.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_jwt.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_lockout.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_mfa_code_repo.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_password.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_sms.py +0 -0
- {regstack-0.2.1 → regstack-0.2.3}/tests/unit/test_smtp_backend.py +0 -0
- {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.
|
|
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
|
|
100
|
-
|
|
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.
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
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-
|
|
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
|
|
36
|
-
|
|
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.
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
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-
|
|
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
|
|
9
|
-
SMS service, a [hooks bus](#hooks),
|
|
10
|
-
router. Hosts
|
|
11
|
-
router(s) wherever
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
105
|
-
`
|
|
106
|
-
`
|
|
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.
|
|
116
|
-
|
|
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
|
|
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`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
175
|
-
|
|
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
|
|
193
|
-
|
|
194
|
-
|
|
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.
|