regstack 0.2.0__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.
- regstack-0.2.2/.github/workflows/test.yml +147 -0
- {regstack-0.2.0 → regstack-0.2.2}/CHANGELOG.md +21 -0
- {regstack-0.2.0 → regstack-0.2.2}/PKG-INFO +9 -11
- {regstack-0.2.0 → regstack-0.2.2}/README.md +8 -10
- {regstack-0.2.0 → regstack-0.2.2}/docs/architecture.md +35 -44
- {regstack-0.2.0 → regstack-0.2.2}/docs/changelog.md +55 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/embedding.md +10 -13
- regstack-0.2.2/docs/index.md +141 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/quickstart.md +11 -11
- {regstack-0.2.0 → regstack-0.2.2}/docs/security.md +49 -68
- {regstack-0.2.0 → regstack-0.2.2}/pyproject.toml +1 -1
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/dependencies.py +3 -4
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/lockout.py +2 -2
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +2 -14
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/pending_repo.py +2 -2
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/user_repo.py +2 -2
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/protocols.py +24 -6
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/repositories/mfa_code_repo.py +1 -4
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/models/_objectid.py +10 -2
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/account.py +1 -1
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/login.py +1 -1
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/phone.py +1 -1
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/register.py +1 -2
- regstack-0.2.2/src/regstack/version.py +1 -0
- regstack-0.2.2/tests/unit/test_base_install_imports.py +69 -0
- {regstack-0.2.0 → regstack-0.2.2}/uv.lock +1 -1
- regstack-0.2.0/.github/workflows/test.yml +0 -86
- regstack-0.2.0/docs/index.md +0 -99
- regstack-0.2.0/src/regstack/version.py +0 -1
- {regstack-0.2.0 → regstack-0.2.2}/.github/workflows/publish.yml +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/.gitignore +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/.python-version +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/.readthedocs.yaml +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/CLAUDE.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/LICENSE +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/NOTICE +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/SECURITY.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/_static/.gitkeep +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/_templates/.gitkeep +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/api.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/cli.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/conf.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/configuration.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/docs/theming.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/_common/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/_common/app.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/mongo/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/mongo/branding/theme.css +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/mongo/main.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/mongo/regstack.toml +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/postgres/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/postgres/main.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/postgres/regstack.toml +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/sqlite/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/sqlite/main.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/examples/sqlite/regstack.toml +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/regstack.toml.example +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/app.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/clock.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/password.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/base.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/factory.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/client.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/indexes.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/migrations/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/migrations/env.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/backends/sql/types.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/__main__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/admin.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/doctor.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/init.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/config/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/config/loader.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/config/schema.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/config/secrets.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/base.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/composer.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/console.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/factory.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/ses.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/smtp.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/hooks/events.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/models/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/models/user.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/admin.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/logout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/password.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/routers/verify.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/sms/base.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/sms/factory.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/sms/null.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/sms/sns.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/pages.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/static/js/regstack.js +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tasks.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/conftest.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_account_management.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_admin_router.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_happy_path.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_indexes.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_mfa.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_sql_migrations.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/integration/test_verification.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_cli.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_jwt.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_lockout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_mfa_code_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_password.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_sms.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_smtp_backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.2}/tests/unit/test_ui_env.py +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: test-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
pytest:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.11", "3.12"]
|
|
20
|
+
services:
|
|
21
|
+
mongodb:
|
|
22
|
+
image: mongo:7
|
|
23
|
+
ports:
|
|
24
|
+
- 27017:27017
|
|
25
|
+
options: >-
|
|
26
|
+
--health-cmd "mongosh --quiet --eval 'db.runCommand({ping:1}).ok' --port 27017"
|
|
27
|
+
--health-interval 5s
|
|
28
|
+
--health-timeout 5s
|
|
29
|
+
--health-retries 12
|
|
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
|
|
45
|
+
env:
|
|
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
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
|
|
52
|
+
- uses: astral-sh/setup-uv@v3
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: true
|
|
55
|
+
cache-dependency-glob: "uv.lock"
|
|
56
|
+
|
|
57
|
+
- name: Pin Python ${{ matrix.python-version }}
|
|
58
|
+
run: uv python pin ${{ matrix.python-version }}
|
|
59
|
+
|
|
60
|
+
- name: Sync dependencies
|
|
61
|
+
run: uv sync --extra dev
|
|
62
|
+
|
|
63
|
+
- name: ruff check
|
|
64
|
+
run: uv run ruff check src tests
|
|
65
|
+
|
|
66
|
+
- name: ruff format check
|
|
67
|
+
run: uv run ruff format --check src tests
|
|
68
|
+
|
|
69
|
+
- name: pytest (parallel, all backends)
|
|
70
|
+
run: uv run python -m pytest -n auto --tb=short
|
|
71
|
+
|
|
72
|
+
docs:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- uses: astral-sh/setup-uv@v3
|
|
77
|
+
with:
|
|
78
|
+
enable-cache: true
|
|
79
|
+
cache-dependency-glob: "uv.lock"
|
|
80
|
+
- run: uv python pin 3.11
|
|
81
|
+
- run: uv sync --extra docs --extra dev
|
|
82
|
+
- run: uv run sphinx-build -b html -W --keep-going docs docs/_build/html
|
|
83
|
+
- uses: actions/upload-artifact@v4
|
|
84
|
+
with:
|
|
85
|
+
name: docs-html
|
|
86
|
+
path: docs/_build/html
|
|
87
|
+
|
|
88
|
+
base-install-smoketest:
|
|
89
|
+
# Catches the kind of regression that broke 0.2.0: an unconditional
|
|
90
|
+
# `from bson import ...` (or any other optional-extra import) at module
|
|
91
|
+
# top level makes `import regstack` fail for any user who didn't opt
|
|
92
|
+
# into the `mongo` extra. The unit test of the same name exercises the
|
|
93
|
+
# equivalent in-process via meta_path blocking; this job verifies the
|
|
94
|
+
# actual built wheel installs and imports against a clean Python.
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v4
|
|
98
|
+
- uses: astral-sh/setup-uv@v3
|
|
99
|
+
with:
|
|
100
|
+
enable-cache: true
|
|
101
|
+
cache-dependency-glob: "uv.lock"
|
|
102
|
+
- run: uv python pin 3.11
|
|
103
|
+
- name: Build wheel
|
|
104
|
+
run: uv build --wheel
|
|
105
|
+
- name: Install wheel into a clean venv (no extras)
|
|
106
|
+
run: |
|
|
107
|
+
python -m venv /tmp/smoke
|
|
108
|
+
/tmp/smoke/bin/pip install --upgrade pip
|
|
109
|
+
/tmp/smoke/bin/pip install dist/regstack-*.whl
|
|
110
|
+
- name: Import smoketest
|
|
111
|
+
run: |
|
|
112
|
+
/tmp/smoke/bin/python -c "
|
|
113
|
+
import regstack
|
|
114
|
+
from regstack import RegStack, RegStackConfig
|
|
115
|
+
assert RegStack.__module__ == 'regstack.app'
|
|
116
|
+
print('ok', regstack.__version__)
|
|
117
|
+
"
|
|
118
|
+
- name: SQLite end-to-end smoketest
|
|
119
|
+
run: |
|
|
120
|
+
/tmp/smoke/bin/python <<'PY'
|
|
121
|
+
import asyncio, secrets, tempfile
|
|
122
|
+
from pathlib import Path
|
|
123
|
+
from regstack import RegStack, RegStackConfig
|
|
124
|
+
from regstack.models.user import BaseUser
|
|
125
|
+
|
|
126
|
+
async def main() -> None:
|
|
127
|
+
with tempfile.TemporaryDirectory() as td:
|
|
128
|
+
cfg = RegStackConfig.load(
|
|
129
|
+
toml_path=Path("/dev/null"),
|
|
130
|
+
secrets_env_path=Path("/dev/null"),
|
|
131
|
+
jwt_secret=secrets.token_urlsafe(64),
|
|
132
|
+
database_url=f"sqlite+aiosqlite:///{td}/rs.db",
|
|
133
|
+
require_verification=False,
|
|
134
|
+
allow_registration=True,
|
|
135
|
+
rate_limit_disabled=True,
|
|
136
|
+
)
|
|
137
|
+
rs = RegStack(config=cfg)
|
|
138
|
+
await rs.install_schema()
|
|
139
|
+
user = await rs.users.create(
|
|
140
|
+
BaseUser(email="a@b.com", hashed_password="h", is_verified=True)
|
|
141
|
+
)
|
|
142
|
+
assert user.id
|
|
143
|
+
await rs.aclose()
|
|
144
|
+
print("sqlite ok")
|
|
145
|
+
|
|
146
|
+
asyncio.run(main())
|
|
147
|
+
PY
|
|
@@ -5,6 +5,27 @@ 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
|
+
|
|
18
|
+
## 0.2.1 — 2026-04-28
|
|
19
|
+
|
|
20
|
+
Hotfix for 0.2.0: `import regstack` failed on a base install because
|
|
21
|
+
several modules in the import path (`models/_objectid.py`,
|
|
22
|
+
`backends/protocols.py`, four routers, and the SQL `mfa_code_repo`)
|
|
23
|
+
had unconditional `from bson …` / `from regstack.backends.mongo …`
|
|
24
|
+
imports — but `pymongo` became an optional `mongo` extra in 0.2.0.
|
|
25
|
+
Added a CI smoketest that builds the wheel and imports it in a
|
|
26
|
+
no-extras venv, plus an in-process regression test that blocks `bson`
|
|
27
|
+
/ `pymongo` via `sys.meta_path`.
|
|
28
|
+
|
|
8
29
|
## 0.2.0 — 2026-04-28
|
|
9
30
|
|
|
10
31
|
Multi-backend support — SQLite (default), Postgres, MongoDB — switched
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regstack
|
|
3
|
-
Version: 0.2.
|
|
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
|
|
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
|
|
|
@@ -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,61 @@
|
|
|
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
|
+
|
|
31
|
+
## 0.2.1 — 2026-04-28
|
|
32
|
+
|
|
33
|
+
**Hotfix for 0.2.0.** `import regstack` was broken on any install that
|
|
34
|
+
didn't include the new `mongo` extra: `models/_objectid.py` imported
|
|
35
|
+
`bson` unconditionally, and four routers + the SQL MFA repo imported
|
|
36
|
+
shared error / enum types out of `regstack.backends.mongo.*`, which
|
|
37
|
+
in turn imports `pymongo` at module top level.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `models/_objectid.py` now imports `bson.ObjectId` lazily inside a
|
|
42
|
+
`try / except ImportError` and only uses it for `isinstance` checks
|
|
43
|
+
when present.
|
|
44
|
+
- `UserAlreadyExistsError`, `PendingAlreadyExistsError`,
|
|
45
|
+
`MfaVerifyOutcome`, and `MfaVerifyResult` moved from their backend
|
|
46
|
+
modules to `regstack.backends.protocols` (the backend-agnostic
|
|
47
|
+
location). Mongo modules re-export them for backwards compatibility.
|
|
48
|
+
- All consumer modules (`routers/register.py`, `routers/account.py`,
|
|
49
|
+
`routers/login.py`, `routers/phone.py`, the SQL MFA repo) updated to
|
|
50
|
+
import from `regstack.backends.protocols`.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- New `base-install-smoketest` CI job: builds the wheel and runs
|
|
55
|
+
`import regstack` + a SQLite end-to-end RegStack lifecycle in a
|
|
56
|
+
fresh venv with **no extras**. Will catch any future regression.
|
|
57
|
+
- New `tests/unit/test_base_install_imports.py` regression test that
|
|
58
|
+
uses `sys.meta_path` to block `bson` / `pymongo` and confirm
|
|
59
|
+
`import regstack` still succeeds.
|
|
60
|
+
|
|
6
61
|
## 0.2.0 — 2026-04-28
|
|
7
62
|
|
|
8
63
|
**Multi-backend support + Alembic migrations.** SQLite is now the
|
|
@@ -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.
|