regstack 0.5.0__tar.gz → 0.5.9__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.5.0 → regstack-0.5.9}/.github/workflows/publish.yml +6 -1
- regstack-0.5.9/CHANGELOG.md +374 -0
- {regstack-0.5.0 → regstack-0.5.9}/PKG-INFO +10 -5
- {regstack-0.5.0 → regstack-0.5.9}/docs/architecture.md +7 -3
- {regstack-0.5.0 → regstack-0.5.9}/docs/changelog.md +233 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/configuration.md +102 -4
- {regstack-0.5.0 → regstack-0.5.9}/docs/quickstart.md +8 -3
- {regstack-0.5.0 → regstack-0.5.9}/docs/security.md +57 -7
- {regstack-0.5.0 → regstack-0.5.9}/pyproject.toml +19 -5
- regstack-0.5.9/scripts/ccr_coverage_setup.py +242 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/app.py +55 -2
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/dependencies.py +12 -4
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/password.py +2 -20
- regstack-0.5.9/src/regstack/auth/rate_limit.py +141 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/backend.py +5 -6
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/client.py +9 -3
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/indexes.py +2 -1
- regstack-0.5.9/src/regstack/backends/mongo/repositories/blacklist_repo.py +43 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +3 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +2 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/oauth_identity_repo.py +3 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/oauth_state_repo.py +14 -3
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/pending_repo.py +3 -16
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/user_repo.py +3 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/protocols.py +14 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/migrations/__init__.py +3 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/migrations/env.py +5 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -2
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/oauth_identity_repo.py +4 -6
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/oauth_state_repo.py +14 -5
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/pending_repo.py +4 -6
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/user_repo.py +4 -4
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/types.py +4 -2
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/doctor.py +9 -5
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/config/schema.py +18 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/composer.py +9 -2
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/ses.py +1 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/hooks/events.py +7 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/_objectid.py +1 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/user.py +14 -1
- regstack-0.5.9/src/regstack/routers/_helpers.py +28 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/account.py +6 -24
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/admin.py +4 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/login.py +12 -5
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/logout.py +3 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/oauth.py +177 -16
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/phone.py +12 -3
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/verify.py +6 -2
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/sms/sns.py +1 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/sms/twilio.py +1 -1
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/pages.py +9 -2
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/static/js/regstack.js +9 -0
- regstack-0.5.9/src/regstack/version.py +1 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/writer.py +1 -3
- {regstack-0.5.0 → regstack-0.5.9}/tasks.py +16 -1
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_admin_router.py +39 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_happy_path.py +40 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_mfa.py +110 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_oauth_google_router.py +126 -0
- regstack-0.5.9/tests/integration/test_rate_limits.py +215 -0
- {regstack-0.5.0 → regstack-0.5.9}/uv.lock +54 -6
- regstack-0.5.0/CHANGELOG.md +0 -145
- regstack-0.5.0/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -28
- regstack-0.5.0/src/regstack/version.py +0 -1
- {regstack-0.5.0 → regstack-0.5.9}/.github/workflows/test.yml +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/.gitignore +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/.python-version +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/.readthedocs.yaml +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/CLAUDE.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/LICENSE +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/NOTICE +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/README.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/SECURITY.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/_static/.gitkeep +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/_templates/.gitkeep +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/api.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/cli.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/conf.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/embedding.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/index.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/oauth.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/security-reports/README.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/docs/theming.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/_common/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/_common/app.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/mongo/README.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/mongo/branding/theme.css +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/mongo/main.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/mongo/regstack.toml +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/postgres/README.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/postgres/main.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/postgres/regstack.toml +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/sqlite/README.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/sqlite/main.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/examples/sqlite/regstack.toml +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/regstack.toml.example +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/scripts/security-review-prompt.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/clock.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/lockout.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/base.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/factory.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/migrations/versions/0002_oauth.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/__main__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/admin.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/init.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/config/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/config/loader.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/config/secrets.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/base.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/console.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/factory.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/smtp.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/oauth_identity.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/oauth_state.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/oauth/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/oauth/base.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/oauth/errors.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/oauth/providers/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/oauth/providers/google.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/oauth/registry.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/password.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/routers/register.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/sms/base.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/sms/factory.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/sms/null.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/oauth_complete.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/cli.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/routes.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/server.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/static/wizard.css +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/static/wizard.js +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/templates/wizard.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/validators.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/window.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/oauth_google/writer.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/cli.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/routes.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/server.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/static/designer.css +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/static/designer.js +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/templates/designer.html +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/validators.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/src/regstack/wizard/theme_designer/window.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tasks/oauth-design.md +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/_fake_google/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/_fake_google/provider.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/conftest.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/e2e/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/e2e/conftest.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/e2e/test_theme_designer.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/e2e/test_wizard_oauth_flow.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_account_management.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_indexes.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_oauth_repos.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_oauth_ui.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_sql_migrations.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/integration/test_verification.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/__init__.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_base_install_imports.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_cli.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_cli_doctor.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_cli_init.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_cli_migrate.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_jwt.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_lockout.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_mfa_code_repo.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_oauth_google.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_password.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_sms.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_smtp_backend.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_theme_designer_cli.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_theme_designer_routes.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_theme_designer_validators.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_theme_designer_writer.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_ui_env.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_wizard_oauth_cli.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_wizard_oauth_routes.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_wizard_oauth_validators.py +0 -0
- {regstack-0.5.0 → regstack-0.5.9}/tests/unit/test_wizard_oauth_writer.py +0 -0
|
@@ -61,4 +61,9 @@ jobs:
|
|
|
61
61
|
with:
|
|
62
62
|
name: dist
|
|
63
63
|
path: dist/
|
|
64
|
-
|
|
64
|
+
# Pinned to a commit SHA, not the mutable `release/v1` branch — the
|
|
65
|
+
# publish job has `id-token: write`, so a tag/branch swap in the
|
|
66
|
+
# action repo would let an attacker push a malicious wheel under our
|
|
67
|
+
# OIDC identity. Update by resolving the latest SHA on `release/v1`
|
|
68
|
+
# and bumping the trailing comment to match.
|
|
69
|
+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The
|
|
4
|
+
authoritative copy lives at
|
|
5
|
+
[`docs/changelog.md`](docs/changelog.md) and is rendered into the
|
|
6
|
+
Sphinx docs.
|
|
7
|
+
|
|
8
|
+
## 0.5.9 — 2026-05-13
|
|
9
|
+
|
|
10
|
+
**`OAuthConfig.enforce_mfa_on_oauth_signin` is now wired.** The flag
|
|
11
|
+
has been on the config since the OAuth router shipped (0.3.0) and the
|
|
12
|
+
wizard surfaced it, but the callback never read it — operators who
|
|
13
|
+
flipped it on still got OAuth sign-ins that bypassed the SMS second
|
|
14
|
+
factor. The High #1 finding from the post-0.5.6 consistency audit.
|
|
15
|
+
|
|
16
|
+
Now, when the flag is `true` and the resolved user has SMS MFA set
|
|
17
|
+
up (`is_mfa_enabled=True` plus a `phone_number`):
|
|
18
|
+
|
|
19
|
+
- The OAuth callback sends the SMS code and stashes a short-lived
|
|
20
|
+
`login_mfa` pending JWT in the state row (instead of a session
|
|
21
|
+
token).
|
|
22
|
+
- `POST /oauth/exchange` returns `mfa_required=True` and
|
|
23
|
+
`mfa_pending_token=...` (with no `access_token`) so the SPA knows
|
|
24
|
+
to redirect to `/account/mfa-confirm`.
|
|
25
|
+
- The SPA's bundled `regstack.js` `oauth-complete` handler stashes
|
|
26
|
+
the pending token under `regstack.mfa_pending` (same key the
|
|
27
|
+
password-login MFA flow uses) and redirects.
|
|
28
|
+
- The user enters the SMS code and hits the existing
|
|
29
|
+
`POST /login/mfa-confirm` endpoint — same downstream path as the
|
|
30
|
+
password-login second factor.
|
|
31
|
+
|
|
32
|
+
Link flows (`mode="link"`) are exempt: the user was already
|
|
33
|
+
authenticated when they kicked off the link, so adding SMS friction
|
|
34
|
+
on top of an already-authenticated link operation has no
|
|
35
|
+
threat-model win.
|
|
36
|
+
|
|
37
|
+
The `ExchangeResponse` model grew two optional fields
|
|
38
|
+
(`mfa_required: bool = False`, `mfa_pending_token: str | None = None`)
|
|
39
|
+
and `access_token` is now defaulted to `""` so the MFA branch can
|
|
40
|
+
return cleanly. Existing handlers reading `access_token` keep
|
|
41
|
+
working — they just need to check `mfa_required` first.
|
|
42
|
+
|
|
43
|
+
## 0.5.8 — 2026-05-13
|
|
44
|
+
|
|
45
|
+
Audit-driven consistency cleanup — small fixes across the API surface
|
|
46
|
+
flagged by the post-0.5.6 consistency review.
|
|
47
|
+
|
|
48
|
+
**Security**
|
|
49
|
+
|
|
50
|
+
- **`oauth.completion_ttl_seconds` is finally enforced.** The flag has
|
|
51
|
+
been on `OAuthConfig` since the OAuth router shipped, but the
|
|
52
|
+
callback never used it: a state row stayed valid for the full
|
|
53
|
+
`state_ttl_seconds` (300s default) between callback completion and
|
|
54
|
+
`/oauth/exchange`. Now `set_result_token(...)` bumps the row's
|
|
55
|
+
expiry down to `now + completion_ttl_seconds` (30s default), so the
|
|
56
|
+
blast radius of a stolen state_id post-callback is the documented
|
|
57
|
+
30-second window. `OAuthStateRepoProtocol.set_result_token` grew an
|
|
58
|
+
optional `new_expires_at=` kwarg to make this atomic with the
|
|
59
|
+
token write.
|
|
60
|
+
|
|
61
|
+
**Changed (UserPublic surface)**
|
|
62
|
+
|
|
63
|
+
- `UserPublic` now serialises `updated_at` and
|
|
64
|
+
`tokens_invalidated_after`. SPAs comparing the latter against their
|
|
65
|
+
cached session JWT's `iat` can detect a forced sign-out after a
|
|
66
|
+
password / email change without an extra round-trip.
|
|
67
|
+
|
|
68
|
+
**Changed (hook payloads)**
|
|
69
|
+
|
|
70
|
+
- `oauth_signin_started` in `mode="link"` now carries the
|
|
71
|
+
authenticated `user=` kwarg, matching `oauth_signin_completed` and
|
|
72
|
+
`oauth_account_linked`. The `mode="signin"` call site stays without
|
|
73
|
+
`user=` (there isn't one yet — sign-in is what produces it).
|
|
74
|
+
|
|
75
|
+
**Internal**
|
|
76
|
+
|
|
77
|
+
- `OAuthConfig.completion_ttl_seconds` config field is now load-bearing
|
|
78
|
+
(was previously declared-but-unread).
|
|
79
|
+
- `MessageResponse` in `routers/oauth.py` deleted; the router now uses
|
|
80
|
+
the shared one from `routers/_schemas.py`. OpenAPI no longer carries
|
|
81
|
+
two identically-named schemas.
|
|
82
|
+
- `MongoOAuthStateRepo` / `SqlOAuthStateRepo` `set_result_token` grew
|
|
83
|
+
a `new_expires_at` parameter (default `None`, so existing callers
|
|
84
|
+
see no change).
|
|
85
|
+
- `MongoBlacklistRepo.purge_expired` switched from `$lte` to `$lt` to
|
|
86
|
+
match the rest of the `purge_expired` family across both backends.
|
|
87
|
+
Edge-instant tokens get one more microsecond of life — the
|
|
88
|
+
bulk-revoke check (which DOES use `<=`) is unchanged.
|
|
89
|
+
- Dead `create()` and `delete_by_id()` methods removed from
|
|
90
|
+
`MongoPendingRepo` — neither was in the protocol or the SQL impl,
|
|
91
|
+
and nothing in src or tests called them.
|
|
92
|
+
- OAuth `start` and `callback` endpoints now declare
|
|
93
|
+
`response_class=RedirectResponse` and `status_code=302`. OpenAPI
|
|
94
|
+
surfaces the redirect intent properly.
|
|
95
|
+
- Custom-claim JWT encoder in `routers/account.py` (email-change
|
|
96
|
+
token) now emits `iat` as a float instead of `int`, matching the
|
|
97
|
+
three other custom-claim encoders and the bulk-revoke contract.
|
|
98
|
+
- `routers/verify.py` `created_at` for resent pending registrations
|
|
99
|
+
now goes through `rs.clock.now()` instead of wall-clock
|
|
100
|
+
`datetime.now(UTC)` — keeps `FrozenClock`-driven tests
|
|
101
|
+
deterministic.
|
|
102
|
+
- `BaseUser.model_config = ConfigDict(extra="allow")` got a
|
|
103
|
+
comment explaining why it's the only model in the package that
|
|
104
|
+
doesn't `extra="forbid"`.
|
|
105
|
+
|
|
106
|
+
## 0.5.7 — 2026-05-13
|
|
107
|
+
|
|
108
|
+
Documentation-only follow-up to 0.5.6.
|
|
109
|
+
|
|
110
|
+
- `docs/configuration.md` now documents the per-route `*_rate_limit`
|
|
111
|
+
family (added in 0.5.4) instead of pointing at
|
|
112
|
+
`login_max_per_minute` / `login_max_per_hour` as reserved future
|
|
113
|
+
fields.
|
|
114
|
+
- `docs/security.md` no longer references
|
|
115
|
+
`PasswordHasher.needs_rehash` (removed in 0.5.6). Replacement
|
|
116
|
+
guidance points hosts at `pwdlib.PasswordHash.verify_and_update`.
|
|
117
|
+
- Root `CHANGELOG.md` backfilled with 0.4.0 and 0.5.0 entries so it
|
|
118
|
+
matches `docs/changelog.md`.
|
|
119
|
+
|
|
120
|
+
## 0.5.6 — 2026-05-13
|
|
121
|
+
|
|
122
|
+
Eleven days of security-review remediation, supply-chain hardening,
|
|
123
|
+
a full `mypy --strict` cleanup pass, and the per-route rate-limits
|
|
124
|
+
feature rolled up into a single release.
|
|
125
|
+
|
|
126
|
+
**Per-route IP rate limits.** Opt-in via the new `rate_limit` extra
|
|
127
|
+
(or a host-supplied `slowapi.Limiter`) plus any of the new
|
|
128
|
+
`RegStackConfig.*_rate_limit` fields (`login_rate_limit`,
|
|
129
|
+
`register_rate_limit`, `forgot_password_rate_limit`,
|
|
130
|
+
`reset_password_rate_limit`, `verify_rate_limit`,
|
|
131
|
+
`resend_verification_rate_limit`, `change_password_rate_limit`,
|
|
132
|
+
`change_email_rate_limit`, `confirm_email_change_rate_limit`,
|
|
133
|
+
`delete_account_rate_limit`). Each accepts a slowapi-syntax string
|
|
134
|
+
(`"5/minute"`, `"5/minute;20/hour"`). Empty / unset means no limit
|
|
135
|
+
on that route — `LockoutService` still defends `/login` against
|
|
136
|
+
credential stuffing per-account. When `*_rate_limit` strings are
|
|
137
|
+
configured but neither a `rate_limiter=` argument is passed nor
|
|
138
|
+
the `rate_limit` extra is installed, `RegStack.router` raises
|
|
139
|
+
`RuntimeError` on first access — failing closed beats silently
|
|
140
|
+
disabling the protection. Hosts remain responsible for
|
|
141
|
+
`app.state.limiter` and `app.add_exception_handler(RateLimitExceeded, ...)`;
|
|
142
|
+
slowapi owns the 429 response shape. The previously-reserved
|
|
143
|
+
`login_max_per_minute` / `login_max_per_hour` fields are kept for
|
|
144
|
+
back-compat but unwired.
|
|
145
|
+
|
|
146
|
+
**Security fixes.**
|
|
147
|
+
|
|
148
|
+
- JWT 401 detail now returns a static `"Invalid or expired token."`;
|
|
149
|
+
no longer leaks the pyjwt failure reason (signature mismatch /
|
|
150
|
+
expired / malformed / audience mismatch).
|
|
151
|
+
- OAuth sign-in now honours `allow_registration=False`. Previously,
|
|
152
|
+
`/register` respected the flag but the OAuth `_resolve_user`
|
|
153
|
+
"brand-new account" branch did not, creating accounts even when
|
|
154
|
+
self-service signup was disabled.
|
|
155
|
+
- Admin `DELETE /admin/users/{id}` now cascades `oauth_identities`,
|
|
156
|
+
matching the user-initiated `DELETE /account` path. Previously
|
|
157
|
+
left orphan rows that blocked re-registration of the same Google
|
|
158
|
+
subject.
|
|
159
|
+
- `POST /phone/start` and `DELETE /phone` now return 400 (not crash
|
|
160
|
+
with HTTP 500) for OAuth-only users who have no `hashed_password`.
|
|
161
|
+
|
|
162
|
+
**Breaking change — hook contracts.** `mfa_login_started` and
|
|
163
|
+
`phone_setup_started` no longer include the raw OTP code in their
|
|
164
|
+
kwargs. Hooks are best-effort observability and are the documented
|
|
165
|
+
integration surface for analytics / logging / Slack notifications,
|
|
166
|
+
so a plaintext OTP in `**kwargs` is a leak waiting to happen.
|
|
167
|
+
Hosts that subscribed to either event to take over SMS delivery
|
|
168
|
+
should migrate to a custom `SmsService` subclass — the supported
|
|
169
|
+
delivery override.
|
|
170
|
+
|
|
171
|
+
**Dependency floors raised for CVEs.**
|
|
172
|
+
|
|
173
|
+
- `pyjwt>=2.12.1` for CVE-2026-32597 (`crit` header bypass, CVSS 7.5).
|
|
174
|
+
- `cryptography>=46.0.7` added explicitly to the `oauth` extra for
|
|
175
|
+
CVE-2026-26007 (ECC subgroup attack on the JWKS code path, CVSS
|
|
176
|
+
8.2) plus CVE-2026-34073 and CVE-2026-39892.
|
|
177
|
+
- `python-multipart>=0.0.26` for CVE-2026-40347 (DoS via oversized
|
|
178
|
+
multipart preamble).
|
|
179
|
+
|
|
180
|
+
**Supply chain.** `pypa/gh-action-pypi-publish` in `publish.yml`
|
|
181
|
+
pinned to a commit SHA instead of the mutable `release/v1` branch.
|
|
182
|
+
The publish job holds `id-token: write`, so a tag/branch swap
|
|
183
|
+
upstream would let an attacker push a malicious wheel under our
|
|
184
|
+
OIDC identity.
|
|
185
|
+
|
|
186
|
+
**Removed.** `PasswordHasher.needs_rehash` — called pwdlib's
|
|
187
|
+
non-existent `check_needs_rehash` and would `AttributeError` if
|
|
188
|
+
anyone invoked it. No callers in src or tests. If you were planning
|
|
189
|
+
to use it, call `pwdlib.PasswordHash.verify_and_update` directly.
|
|
190
|
+
|
|
191
|
+
**Internal.** 72 `mypy --strict` errors cleared across 35 files;
|
|
192
|
+
`inv lint` is now green end-to-end. Mongo
|
|
193
|
+
`BlacklistRepo.purge_expired` added (protocol parity with SQL).
|
|
194
|
+
`KNOWN_EVENTS` reconciled — 7 previously-undeclared events added
|
|
195
|
+
(`verification_requested`, `email_change_requested`, `email_changed`,
|
|
196
|
+
`phone_setup_started`, `mfa_login_started`, `mfa_enabled`,
|
|
197
|
+
`mfa_disabled`). `user_logged_out` now actually fires from
|
|
198
|
+
`routers/logout.py` (was listed in `KNOWN_EVENTS` but no router
|
|
199
|
+
emitted it).
|
|
200
|
+
|
|
201
|
+
## 0.5.0 — 2026-05-02
|
|
202
|
+
|
|
203
|
+
**Theme designer.** `regstack theme design` opens a native pywebview
|
|
204
|
+
window with controls for every `--rs-*` CSS custom property and a
|
|
205
|
+
real-time preview of the bundled SSR widgets (sign-in form, success /
|
|
206
|
+
error banners, danger-zone button). Saving writes `regstack-theme.css`;
|
|
207
|
+
the designer round-trips values back into the form on next launch so
|
|
208
|
+
iteration is non-destructive. `--print-only` mode takes repeatable
|
|
209
|
+
`--var NAME=VALUE` pairs (with a `dark:` prefix for dark-scheme
|
|
210
|
+
overrides) and writes the file headlessly. Lives in
|
|
211
|
+
`regstack.wizard.theme_designer`; registered as a lazy Click subgroup
|
|
212
|
+
so `regstack init` / `doctor` don't pay the pywebview/uvicorn import
|
|
213
|
+
cost.
|
|
214
|
+
|
|
215
|
+
**Docs.** New "About the examples" convention block at the top of
|
|
216
|
+
`docs/index.md`. Every URL, email, smtp host, and admin command across
|
|
217
|
+
the docs now extrapolates from the same fictional app at
|
|
218
|
+
`app.example.com` with `<username>` / `<password>` placeholders.
|
|
219
|
+
|
|
220
|
+
## 0.4.0 — 2026-05-02
|
|
221
|
+
|
|
222
|
+
**OAuth setup wizard.** `regstack oauth setup` opens a native webview
|
|
223
|
+
window that walks an operator through registering a Google OAuth 2.0
|
|
224
|
+
client and merges the credentials into `regstack.toml` +
|
|
225
|
+
`regstack.secrets.env` non-destructively (preserves comments, other
|
|
226
|
+
tables, unrelated keys). 12-step SPA inside a local-only 127.0.0.1
|
|
227
|
+
FastAPI server, gated by a per-launch random token. Each "Next" click
|
|
228
|
+
hits a server-side validator so the Write step can never be reached
|
|
229
|
+
with bad data. `--print-only` mode skips the GUI for headless / CI
|
|
230
|
+
use.
|
|
231
|
+
|
|
232
|
+
Three new base dependencies — `pywebview>=5.0`, `tomlkit>=0.13`,
|
|
233
|
+
`uvicorn[standard]>=0.29` — for the wizard's local server.
|
|
234
|
+
`pytest-playwright` added to the `dev` extra; new `inv test-e2e` task
|
|
235
|
+
chained into `inv test-all`.
|
|
236
|
+
|
|
237
|
+
## 0.3.0 — 2026-04-30
|
|
238
|
+
|
|
239
|
+
**OAuth — Sign in with Google.** Opt-in via the new `oauth` extra
|
|
240
|
+
and `enable_oauth=True`. Five JSON endpoints, an SSR
|
|
241
|
+
`/account/oauth-complete` page, "Sign in with Google" button on the
|
|
242
|
+
login page, and a Connected-accounts panel on `/account/me`.
|
|
243
|
+
|
|
244
|
+
Schema migration `0002_oauth.py` creates `oauth_identities` +
|
|
245
|
+
`oauth_states` and makes `users.hashed_password` nullable
|
|
246
|
+
(OAuth-only users have no password). Roll forward via
|
|
247
|
+
`regstack migrate` or first-boot `install_schema()` — no manual
|
|
248
|
+
intervention.
|
|
249
|
+
|
|
250
|
+
Account-linking policy defaults to **refuse**: if a Google sign-in
|
|
251
|
+
arrives carrying an email that already belongs to a password-
|
|
252
|
+
registered user, the callback returns `?error=email_in_use` and the
|
|
253
|
+
user must sign in then explicitly link from `/account/me`. Hosts
|
|
254
|
+
that consciously accept the email-recycling threat for UX can flip
|
|
255
|
+
`oauth.auto_link_verified_emails = true`. See
|
|
256
|
+
[`docs/oauth.md`](https://regstack.readthedocs.io/en/latest/oauth.html)
|
|
257
|
+
and [`tasks/oauth-design.md`](https://github.com/jdrumgoole/regstack/blob/main/tasks/oauth-design.md)
|
|
258
|
+
for the full threat model.
|
|
259
|
+
|
|
260
|
+
**Migration**
|
|
261
|
+
|
|
262
|
+
- Install the new extra: `uv add 'regstack[oauth]'`.
|
|
263
|
+
- Set `enable_oauth = true` and provide `oauth.google_client_id` +
|
|
264
|
+
`oauth.google_client_secret`.
|
|
265
|
+
- Run `regstack migrate` (SQL backends only) or rely on
|
|
266
|
+
`install_schema()` at first boot.
|
|
267
|
+
|
|
268
|
+
`BaseUser.hashed_password` is now `str | None`. Code that imported
|
|
269
|
+
the field type explicitly will need to widen it.
|
|
270
|
+
|
|
271
|
+
## 0.2.6 — 2026-04-28
|
|
272
|
+
|
|
273
|
+
Bug fix.
|
|
274
|
+
|
|
275
|
+
- **Fix:** `/admin/stats` reported `pending_registrations: 0` on
|
|
276
|
+
every SQL backend. The route reached into the Mongo repo's private
|
|
277
|
+
`_collection` attribute and silently fell back to `0` when the
|
|
278
|
+
attribute was absent. Added `count_unexpired(now=None)` to
|
|
279
|
+
`PendingRepoProtocol` with Mongo + SQL implementations and routed
|
|
280
|
+
through `rs.clock.now()` so the count respects the injected clock.
|
|
281
|
+
New parametrized integration test exercises the count on every
|
|
282
|
+
backend.
|
|
283
|
+
|
|
284
|
+
## 0.2.5 — 2026-04-28
|
|
285
|
+
|
|
286
|
+
Bug fix + tooling.
|
|
287
|
+
|
|
288
|
+
- **Fix:** `regstack doctor` against a SQL backend crashed with
|
|
289
|
+
`asyncio.run() cannot be called from a running event loop`. The
|
|
290
|
+
schema check called `regstack.backends.sql.migrations.current()`,
|
|
291
|
+
which used `asyncio.run()` internally — invalid inside doctor's own
|
|
292
|
+
`asyncio.run`. Added `current_async()` and switched the doctor
|
|
293
|
+
command to use it. Sync `current()` is preserved for the migrate
|
|
294
|
+
CLI.
|
|
295
|
+
- **New:** `inv coverage [--no-html] [--fail-under=N]` runs the full
|
|
296
|
+
three-backend matrix under coverage and writes term + HTML reports.
|
|
297
|
+
Branch coverage is on by default.
|
|
298
|
+
- Test coverage uplift on the CLI: `cli/init.py` 14% → 88%,
|
|
299
|
+
`cli/doctor.py` 61% → 87%. Total: **85% → 87.1%**.
|
|
300
|
+
|
|
301
|
+
## 0.2.4 — 2026-04-28
|
|
302
|
+
|
|
303
|
+
**Breaking** — back-compat shims removed:
|
|
304
|
+
|
|
305
|
+
- `RegStack.install_indexes()` (alias for `install_schema()`).
|
|
306
|
+
- `ObjectIdStr` alias for `IdStr` in `regstack.models._objectid`.
|
|
307
|
+
- Re-exports of `UserAlreadyExistsError`,
|
|
308
|
+
`PendingAlreadyExistsError`, `MfaVerifyOutcome`, and
|
|
309
|
+
`MfaVerifyResult` from `regstack.backends.mongo.repositories.*`.
|
|
310
|
+
Their canonical home is `regstack.backends.protocols`.
|
|
311
|
+
|
|
312
|
+
If you import any of these from the old paths, switch to:
|
|
313
|
+
- `RegStack.install_schema()`
|
|
314
|
+
- `from regstack.models._objectid import IdStr`
|
|
315
|
+
- `from regstack.backends.protocols import UserAlreadyExistsError`
|
|
316
|
+
(and friends).
|
|
317
|
+
|
|
318
|
+
The internal mongo `install_indexes(db, config)` function is unchanged.
|
|
319
|
+
|
|
320
|
+
## 0.2.3 — 2026-04-28
|
|
321
|
+
|
|
322
|
+
Docs-only release. Restructured the API reference around the current
|
|
323
|
+
package layout (post multi-backend refactor) and added Google-style
|
|
324
|
+
docstrings (Args / Returns / Raises) to the public surface — RegStack,
|
|
325
|
+
JwtCodec, PasswordHasher, LockoutService, AuthDependencies,
|
|
326
|
+
HookRegistry, EmailService, SmsService, the router builders, and the
|
|
327
|
+
Clock implementations. Dataclass field docs moved to PEP 258
|
|
328
|
+
attribute docstrings. Sphinx builds clean under `-W` again.
|
|
329
|
+
|
|
330
|
+
## 0.2.2 — 2026-04-28
|
|
331
|
+
|
|
332
|
+
Docs-only release. The README and Sphinx docs landing page now lead
|
|
333
|
+
with the same pitch (problem framing, "Why not just use…?" comparison
|
|
334
|
+
vs Auth0 / Clerk / Keycloak / fastapi-users) before diving into
|
|
335
|
+
architecture. Hyperlink density trimmed back: only major external
|
|
336
|
+
packages, products, and JWT (RFC 7519) are linked — Wikipedia trivia,
|
|
337
|
+
MDN basics, OWASP article links, and deep-dependency helper-class
|
|
338
|
+
docs were removed.
|
|
339
|
+
|
|
340
|
+
## 0.2.1 — 2026-04-28
|
|
341
|
+
|
|
342
|
+
Hotfix for 0.2.0: `import regstack` failed on a base install because
|
|
343
|
+
several modules in the import path (`models/_objectid.py`,
|
|
344
|
+
`backends/protocols.py`, four routers, and the SQL `mfa_code_repo`)
|
|
345
|
+
had unconditional `from bson …` / `from regstack.backends.mongo …`
|
|
346
|
+
imports — but `pymongo` became an optional `mongo` extra in 0.2.0.
|
|
347
|
+
Added a CI smoketest that builds the wheel and imports it in a
|
|
348
|
+
no-extras venv, plus an in-process regression test that blocks `bson`
|
|
349
|
+
/ `pymongo` via `sys.meta_path`.
|
|
350
|
+
|
|
351
|
+
## 0.2.0 — 2026-04-28
|
|
352
|
+
|
|
353
|
+
Multi-backend support — SQLite (default), Postgres, MongoDB — switched
|
|
354
|
+
by `database_url` URL scheme. Bundled Alembic migrations for SQL
|
|
355
|
+
backends. Embedding API change: `RegStack(config=, db=)` →
|
|
356
|
+
`RegStack(config=, backend=None)`. README + core docs rewritten for
|
|
357
|
+
less-expert readers (problem framing, hyperlinks to external
|
|
358
|
+
standards, comparison vs Auth0/Clerk/Keycloak/fastapi-users).
|
|
359
|
+
|
|
360
|
+
See [`docs/changelog.md`](docs/changelog.md) for the full per-feature
|
|
361
|
+
breakdown.
|
|
362
|
+
|
|
363
|
+
## 0.1.1 — 2026-04-27
|
|
364
|
+
|
|
365
|
+
- Rewrite README relative links as absolute URLs so they resolve on the
|
|
366
|
+
PyPI project page. README-only release.
|
|
367
|
+
|
|
368
|
+
## 0.1.0 — 2026-04-27
|
|
369
|
+
|
|
370
|
+
First tagged release. Bundles M1–M6 from the development plan into a
|
|
371
|
+
single Apache-2.0 package on PyPI.
|
|
372
|
+
|
|
373
|
+
See [`docs/changelog.md`](docs/changelog.md) for the per-milestone
|
|
374
|
+
breakdown of M1 through M6.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regstack
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.9
|
|
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
|
|
@@ -27,8 +27,8 @@ Requires-Dist: jinja2>=3.1
|
|
|
27
27
|
Requires-Dist: pwdlib[argon2]>=0.2.1
|
|
28
28
|
Requires-Dist: pydantic-settings>=2.2
|
|
29
29
|
Requires-Dist: pydantic>=2.6
|
|
30
|
-
Requires-Dist: pyjwt>=2.
|
|
31
|
-
Requires-Dist: python-multipart>=0.0.
|
|
30
|
+
Requires-Dist: pyjwt>=2.12.1
|
|
31
|
+
Requires-Dist: python-multipart>=0.0.26
|
|
32
32
|
Requires-Dist: pywebview>=5.0
|
|
33
33
|
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
34
34
|
Requires-Dist: tomlkit>=0.13
|
|
@@ -36,10 +36,11 @@ Requires-Dist: uvicorn[standard]>=0.29
|
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: anyio>=4.3; extra == 'dev'
|
|
38
38
|
Requires-Dist: asyncpg>=0.29; extra == 'dev'
|
|
39
|
+
Requires-Dist: cryptography>=46.0.7; extra == 'dev'
|
|
39
40
|
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
40
41
|
Requires-Dist: invoke>=2.2; extra == 'dev'
|
|
41
42
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
42
|
-
Requires-Dist: pyjwt[crypto]>=2.
|
|
43
|
+
Requires-Dist: pyjwt[crypto]>=2.12.1; extra == 'dev'
|
|
43
44
|
Requires-Dist: pymongo>=4.9; extra == 'dev'
|
|
44
45
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
45
46
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
@@ -47,6 +48,7 @@ Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
|
|
|
47
48
|
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
|
|
48
49
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
49
50
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
51
|
+
Requires-Dist: slowapi>=0.1.9; extra == 'dev'
|
|
50
52
|
Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
|
|
51
53
|
Provides-Extra: docs
|
|
52
54
|
Requires-Dist: furo>=2024.1; extra == 'docs'
|
|
@@ -58,9 +60,12 @@ Requires-Dist: sphinx>=7.3; extra == 'docs'
|
|
|
58
60
|
Provides-Extra: mongo
|
|
59
61
|
Requires-Dist: pymongo>=4.9; extra == 'mongo'
|
|
60
62
|
Provides-Extra: oauth
|
|
61
|
-
Requires-Dist:
|
|
63
|
+
Requires-Dist: cryptography>=46.0.7; extra == 'oauth'
|
|
64
|
+
Requires-Dist: pyjwt[crypto]>=2.12.1; extra == 'oauth'
|
|
62
65
|
Provides-Extra: postgres
|
|
63
66
|
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
67
|
+
Provides-Extra: rate-limit
|
|
68
|
+
Requires-Dist: slowapi>=0.1.9; extra == 'rate-limit'
|
|
64
69
|
Provides-Extra: ses
|
|
65
70
|
Requires-Dist: aioboto3>=12.3; extra == 'ses'
|
|
66
71
|
Provides-Extra: sns
|
|
@@ -54,9 +54,13 @@ multi-tenant deployments where a single FastAPI app serves multiple
|
|
|
54
54
|
The backend is auto-built from `config.database_url` if not supplied
|
|
55
55
|
explicitly. URL scheme decides:
|
|
56
56
|
|
|
57
|
-
- `sqlite+aiosqlite
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
- `sqlite+aiosqlite:///PATH` → SQLAlchemy backend in SQLite mode.
|
|
58
|
+
`PATH` is `./dbname.db` for a relative file, `/var/lib/app/dbname.db`
|
|
59
|
+
for an absolute file, or `:memory:` for an ephemeral in-process DB.
|
|
60
|
+
- `postgresql+asyncpg://<username>:<password>@dbhost.example.com:5432/dbname`
|
|
61
|
+
→ SQLAlchemy backend in Postgres mode.
|
|
62
|
+
- `mongodb://<username>:<password>@dbhost.example.com:27017/dbname`
|
|
63
|
+
(or `mongodb+srv://…`) → Mongo backend.
|
|
60
64
|
|
|
61
65
|
The façade exposes:
|
|
62
66
|
|