regstack 0.5.6__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {regstack-0.5.6 → regstack-0.6.0}/.github/workflows/publish.yml +18 -5
- {regstack-0.5.6 → regstack-0.6.0}/.github/workflows/test.yml +21 -7
- {regstack-0.5.6 → regstack-0.6.0}/.gitignore +12 -0
- regstack-0.6.0/CHANGELOG.md +463 -0
- {regstack-0.5.6 → regstack-0.6.0}/PKG-INFO +9 -6
- {regstack-0.5.6 → regstack-0.6.0}/docs/changelog.md +208 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/configuration.md +64 -2
- {regstack-0.5.6 → regstack-0.6.0}/docs/security.md +7 -4
- {regstack-0.5.6 → regstack-0.6.0}/pyproject.toml +22 -8
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/blacklist_repo.py +4 -1
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/oauth_state_repo.py +11 -2
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -15
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/protocols.py +14 -1
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/oauth_state_repo.py +11 -2
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/__main__.py +37 -7
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/user.py +13 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/account.py +20 -7
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/admin.py +15 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/login.py +17 -5
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/oauth.py +199 -18
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/verify.py +6 -2
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/static/js/regstack.js +9 -0
- regstack-0.6.0/src/regstack/version.py +1 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_account_management.py +32 -12
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_admin_router.py +31 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_oauth_google_router.py +119 -0
- regstack-0.6.0/tests/unit/test_cli_wizard_missing_extra.py +59 -0
- {regstack-0.5.6 → regstack-0.6.0}/uv.lock +16 -10
- regstack-0.5.6/CHANGELOG.md +0 -226
- regstack-0.5.6/src/regstack/version.py +0 -1
- {regstack-0.5.6 → regstack-0.6.0}/.python-version +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/.readthedocs.yaml +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/CLAUDE.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/LICENSE +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/NOTICE +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/README.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/SECURITY.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/_static/.gitkeep +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/_templates/.gitkeep +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/api.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/architecture.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/cli.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/conf.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/embedding.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/index.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/oauth.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/quickstart.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/security-reports/README.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/docs/theming.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/_common/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/_common/app.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/mongo/README.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/mongo/branding/theme.css +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/mongo/main.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/mongo/regstack.toml +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/postgres/README.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/postgres/main.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/postgres/regstack.toml +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/sqlite/README.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/sqlite/main.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/examples/sqlite/regstack.toml +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/regstack.toml.example +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/scripts/ccr_coverage_setup.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/scripts/security-review-prompt.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/app.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/clock.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/dependencies.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/lockout.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/password.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/rate_limit.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/base.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/factory.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/backend.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/client.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/indexes.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/oauth_identity_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/migrations/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/migrations/env.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/migrations/versions/0002_oauth.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/oauth_identity_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/backends/sql/types.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/admin.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/doctor.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/init.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/config/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/config/loader.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/config/schema.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/config/secrets.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/base.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/composer.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/console.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/factory.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/ses.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/smtp.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/hooks/events.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/_objectid.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/oauth_identity.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/oauth_state.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/oauth/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/oauth/base.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/oauth/errors.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/oauth/providers/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/oauth/providers/google.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/oauth/registry.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/_helpers.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/logout.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/password.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/phone.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/routers/register.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/sms/base.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/sms/factory.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/sms/null.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/sms/sns.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/pages.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/oauth_complete.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/cli.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/routes.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/server.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/static/wizard.css +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/static/wizard.js +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/templates/wizard.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/validators.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/window.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/oauth_google/writer.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/cli.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/routes.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/server.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/static/designer.css +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/static/designer.js +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/templates/designer.html +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/validators.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/window.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/src/regstack/wizard/theme_designer/writer.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tasks/oauth-design.md +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tasks.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/_fake_google/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/_fake_google/provider.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/conftest.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/e2e/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/e2e/conftest.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/e2e/test_theme_designer.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/e2e/test_wizard_oauth_flow.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_happy_path.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_indexes.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_mfa.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_oauth_repos.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_oauth_ui.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_rate_limits.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_sql_migrations.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/integration/test_verification.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/__init__.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_base_install_imports.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_cli.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_cli_doctor.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_cli_init.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_cli_migrate.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_jwt.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_lockout.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_mfa_code_repo.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_oauth_google.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_password.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_sms.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_smtp_backend.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_theme_designer_cli.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_theme_designer_routes.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_theme_designer_validators.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_theme_designer_writer.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_ui_env.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_wizard_oauth_cli.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_wizard_oauth_routes.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_wizard_oauth_validators.py +0 -0
- {regstack-0.5.6 → regstack-0.6.0}/tests/unit/test_wizard_oauth_writer.py +0 -0
|
@@ -4,6 +4,11 @@ name: publish
|
|
|
4
4
|
# Configure the PyPI project (https://pypi.org/manage/account/publishing/)
|
|
5
5
|
# with the publisher: this repository, environment name `pypi`, workflow
|
|
6
6
|
# `publish.yml`. No PyPI tokens live in GitHub Actions secrets.
|
|
7
|
+
#
|
|
8
|
+
# Third-party actions are pinned to commit SHAs (not mutable tags) so a
|
|
9
|
+
# tag swap upstream can't substitute a malicious version. Update by
|
|
10
|
+
# resolving the latest SHA for the version you want and bumping the
|
|
11
|
+
# trailing `# vN` comment to match.
|
|
7
12
|
|
|
8
13
|
on:
|
|
9
14
|
push:
|
|
@@ -11,15 +16,22 @@ on:
|
|
|
11
16
|
- "v*"
|
|
12
17
|
workflow_dispatch:
|
|
13
18
|
|
|
19
|
+
# Workflow-level default. Individual jobs override where they need
|
|
20
|
+
# extra scopes (the `publish` job adds `id-token: write` for OIDC).
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
14
24
|
jobs:
|
|
15
25
|
build:
|
|
16
26
|
runs-on: ubuntu-latest
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
17
29
|
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
19
31
|
with:
|
|
20
32
|
fetch-depth: 0
|
|
21
33
|
|
|
22
|
-
- uses: astral-sh/setup-uv@v3
|
|
34
|
+
- uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
|
23
35
|
with:
|
|
24
36
|
enable-cache: true
|
|
25
37
|
|
|
@@ -42,7 +54,7 @@ jobs:
|
|
|
42
54
|
- name: Build sdist + wheel
|
|
43
55
|
run: uv build
|
|
44
56
|
|
|
45
|
-
- uses: actions/upload-artifact@v4
|
|
57
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
46
58
|
with:
|
|
47
59
|
name: dist
|
|
48
60
|
path: dist/
|
|
@@ -55,9 +67,10 @@ jobs:
|
|
|
55
67
|
name: pypi
|
|
56
68
|
url: https://pypi.org/p/regstack
|
|
57
69
|
permissions:
|
|
58
|
-
id-token: write # OIDC trusted-publisher
|
|
70
|
+
id-token: write # OIDC trusted-publisher exchange
|
|
71
|
+
contents: read
|
|
59
72
|
steps:
|
|
60
|
-
- uses: actions/download-artifact@v4
|
|
73
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
61
74
|
with:
|
|
62
75
|
name: dist
|
|
63
76
|
path: dist/
|
|
@@ -10,9 +10,19 @@ concurrency:
|
|
|
10
10
|
group: test-${{ github.ref }}
|
|
11
11
|
cancel-in-progress: true
|
|
12
12
|
|
|
13
|
+
# Workflow-level default — every job in this workflow gets read-only
|
|
14
|
+
# permissions unless it overrides explicitly. Third-party actions are
|
|
15
|
+
# pinned to commit SHAs so a tag swap upstream can't substitute a
|
|
16
|
+
# malicious version; update by resolving the latest SHA for the
|
|
17
|
+
# version you want and bumping the trailing `# vN` comment.
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
13
21
|
jobs:
|
|
14
22
|
pytest:
|
|
15
23
|
runs-on: ubuntu-latest
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
16
26
|
strategy:
|
|
17
27
|
fail-fast: false
|
|
18
28
|
matrix:
|
|
@@ -47,9 +57,9 @@ jobs:
|
|
|
47
57
|
# Connect as superuser so the per-test fixture can CREATE/DROP DATABASE.
|
|
48
58
|
REGSTACK_TEST_POSTGRES_URL: postgresql+asyncpg://regstack:regstack@localhost:5432
|
|
49
59
|
steps:
|
|
50
|
-
- uses: actions/checkout@v4
|
|
60
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
51
61
|
|
|
52
|
-
- uses: astral-sh/setup-uv@v3
|
|
62
|
+
- uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
|
53
63
|
with:
|
|
54
64
|
enable-cache: true
|
|
55
65
|
cache-dependency-glob: "uv.lock"
|
|
@@ -71,16 +81,18 @@ jobs:
|
|
|
71
81
|
|
|
72
82
|
docs:
|
|
73
83
|
runs-on: ubuntu-latest
|
|
84
|
+
permissions:
|
|
85
|
+
contents: read
|
|
74
86
|
steps:
|
|
75
|
-
- uses: actions/checkout@v4
|
|
76
|
-
- uses: astral-sh/setup-uv@v3
|
|
87
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
88
|
+
- uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
|
77
89
|
with:
|
|
78
90
|
enable-cache: true
|
|
79
91
|
cache-dependency-glob: "uv.lock"
|
|
80
92
|
- run: uv python pin 3.11
|
|
81
93
|
- run: uv sync --extra docs --extra dev
|
|
82
94
|
- run: uv run sphinx-build -b html -W --keep-going docs docs/_build/html
|
|
83
|
-
- uses: actions/upload-artifact@v4
|
|
95
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
84
96
|
with:
|
|
85
97
|
name: docs-html
|
|
86
98
|
path: docs/_build/html
|
|
@@ -93,9 +105,11 @@ jobs:
|
|
|
93
105
|
# equivalent in-process via meta_path blocking; this job verifies the
|
|
94
106
|
# actual built wheel installs and imports against a clean Python.
|
|
95
107
|
runs-on: ubuntu-latest
|
|
108
|
+
permissions:
|
|
109
|
+
contents: read
|
|
96
110
|
steps:
|
|
97
|
-
- uses: actions/checkout@v4
|
|
98
|
-
- uses: astral-sh/setup-uv@v3
|
|
111
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
112
|
+
- uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
|
99
113
|
with:
|
|
100
114
|
enable-cache: true
|
|
101
115
|
cache-dependency-glob: "uv.lock"
|
|
@@ -32,3 +32,15 @@ docs/_autosummary/
|
|
|
32
32
|
/regstack-bootstrap.json
|
|
33
33
|
**/regstack.secrets.env
|
|
34
34
|
**/regstack-bootstrap.json
|
|
35
|
+
|
|
36
|
+
# Defensive: keep credential material out of any commit, even from a
|
|
37
|
+
# misconfigured local dev env. None of these are checked in today, but
|
|
38
|
+
# the .env / *.pem / etc. patterns are recurring audit recommendations.
|
|
39
|
+
.env
|
|
40
|
+
.env.*
|
|
41
|
+
*.pem
|
|
42
|
+
*.key
|
|
43
|
+
*.p12
|
|
44
|
+
*.pfx
|
|
45
|
+
*.jks
|
|
46
|
+
*.crt
|
|
@@ -0,0 +1,463 @@
|
|
|
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.6.0 — 2026-05-14
|
|
9
|
+
|
|
10
|
+
**Breaking change for wizard users.** The GUI setup wizards
|
|
11
|
+
(`regstack oauth setup`, `regstack theme design`) are now behind a
|
|
12
|
+
new optional `wizard` extra. `pip install regstack` no longer pulls
|
|
13
|
+
in `pywebview`, `tomlkit`, or `uvicorn[standard]` — three heavy
|
|
14
|
+
wizard-only dependencies that every library consumer was paying for,
|
|
15
|
+
including a platform browser engine on every fresh install. A
|
|
16
|
+
recurring audit recommendation since 0.5.0.
|
|
17
|
+
|
|
18
|
+
**Migration.**
|
|
19
|
+
|
|
20
|
+
- If you only embed regstack in a FastAPI app (no `regstack oauth
|
|
21
|
+
setup` or `regstack theme design`): no action needed. The base
|
|
22
|
+
install is now significantly slimmer.
|
|
23
|
+
- If you use either setup wizard: install the new extra —
|
|
24
|
+
`pip install 'regstack[wizard]'` or `uv sync --extra wizard`.
|
|
25
|
+
Running a wizard subcommand without the extra now exits with a
|
|
26
|
+
one-line install hint (no ImportError traceback).
|
|
27
|
+
- The `dev` extra continues to pull in the wizard deps directly so
|
|
28
|
+
`inv test-all` keeps working without an explicit `--extra wizard`.
|
|
29
|
+
|
|
30
|
+
Bumped to **0.6.0** (not 0.5.12) because removing top-level deps is
|
|
31
|
+
the kind of change that can surprise downstream `pip install
|
|
32
|
+
regstack` callers — even though "the GUI wizard CLIs need an extra
|
|
33
|
+
now" is the only observable effect.
|
|
34
|
+
|
|
35
|
+
## 0.5.11 — 2026-05-14
|
|
36
|
+
|
|
37
|
+
CI / workflow hygiene. No runtime code changes.
|
|
38
|
+
|
|
39
|
+
- **All third-party GitHub Actions pinned to commit SHAs.**
|
|
40
|
+
`actions/checkout@v4`, `astral-sh/setup-uv@v3`,
|
|
41
|
+
`actions/upload-artifact@v4`, and `actions/download-artifact@v4` now
|
|
42
|
+
use commit SHAs in `.github/workflows/publish.yml` and
|
|
43
|
+
`.github/workflows/test.yml`, with `# v4` / `# v3` trailing comments
|
|
44
|
+
so future operators can resolve and bump. `pypa/gh-action-pypi-publish`
|
|
45
|
+
was already SHA-pinned (#37). A tag swap upstream can no longer
|
|
46
|
+
substitute a malicious version.
|
|
47
|
+
- **`permissions:` blocks added to every workflow + job.** Both
|
|
48
|
+
workflows now declare a `permissions: contents: read` default at
|
|
49
|
+
the workflow level and re-state it per job (so a future addition of
|
|
50
|
+
a write-needing action doesn't silently inherit elevated scopes).
|
|
51
|
+
The `publish` job continues to declare `id-token: write` (OIDC
|
|
52
|
+
trusted-publisher exchange) — that's the only scope above
|
|
53
|
+
read-only anywhere in the workflows.
|
|
54
|
+
- **`.gitignore` defensive additions.** `.env`, `.env.*`, and the
|
|
55
|
+
common credential-file patterns (`*.pem`, `*.key`, `*.p12`,
|
|
56
|
+
`*.pfx`, `*.jks`, `*.crt`) are now ignored at the repo root. None
|
|
57
|
+
are present today; this is belt-and-braces for misconfigured local
|
|
58
|
+
dev environments. A recurring audit recommendation.
|
|
59
|
+
|
|
60
|
+
## 0.5.10 — 2026-05-14
|
|
61
|
+
|
|
62
|
+
Security fixes from the 2026-05-13 / 2026-05-14 daily reviews. All
|
|
63
|
+
warnings, no criticals — but several are real exploitable issues.
|
|
64
|
+
|
|
65
|
+
**Security.**
|
|
66
|
+
|
|
67
|
+
- **Open-redirect bypass in OAuth `redirect_to`.** `_validate_redirect`
|
|
68
|
+
was forwarding `urlsplit`'s judgment, but browsers normalize values
|
|
69
|
+
like `/\evil.com` and `////evil.com` into the protocol-relative
|
|
70
|
+
`//evil.com` — both of which `urlsplit` reports as same-origin
|
|
71
|
+
paths. The validator now rejects any backslash plus any value that
|
|
72
|
+
doesn't start with a single `/` followed by a non-slash character.
|
|
73
|
+
- **CVE-2025-62727 — `fastapi` floor raised to `>=0.120.0`.** Starlette
|
|
74
|
+
DoS via large request bodies after multipart processing.
|
|
75
|
+
- **CVE-2025-27516 — `jinja2` floor raised to `>=3.1.6`.** Sandbox
|
|
76
|
+
breakout via the `|attr` filter (only relevant if hosts allow
|
|
77
|
+
user-controlled templates; tightening the floor regardless).
|
|
78
|
+
- **Login lockout no longer skips disabled / unverified accounts.**
|
|
79
|
+
`POST /login` now records a failure before raising HTTP 403 for
|
|
80
|
+
`is_active=False` and (when `require_verification=True`)
|
|
81
|
+
`is_verified=False` users. Password verification was also re-ordered
|
|
82
|
+
to run **before** those checks, so an attacker without the password
|
|
83
|
+
can't distinguish disabled vs active accounts by HTTP code alone.
|
|
84
|
+
- **`POST /change-email` no longer enumerates registered addresses.**
|
|
85
|
+
An authenticated attacker could previously iterate the email
|
|
86
|
+
namespace via the 409 vs 202 response distinction. The endpoint now
|
|
87
|
+
always returns 202; if the candidate is already registered, no
|
|
88
|
+
confirmation email is sent (the legitimate user finds out by not
|
|
89
|
+
receiving it). Matches the existing anti-enumeration stance on
|
|
90
|
+
`/forgot-password` and `/resend-verification`.
|
|
91
|
+
- **Admin resend-verification rejects OAuth-only users.** Previously
|
|
92
|
+
attempted to construct a `PendingRegistration` from a user with
|
|
93
|
+
`hashed_password=None`, which either failed validation or stored
|
|
94
|
+
the literal string `"None"` in the pending row. Now returns 400
|
|
95
|
+
with a clear message.
|
|
96
|
+
|
|
97
|
+
## 0.5.9 — 2026-05-13
|
|
98
|
+
|
|
99
|
+
**`OAuthConfig.enforce_mfa_on_oauth_signin` is now wired.** The flag
|
|
100
|
+
has been on the config since the OAuth router shipped (0.3.0) and the
|
|
101
|
+
wizard surfaced it, but the callback never read it — operators who
|
|
102
|
+
flipped it on still got OAuth sign-ins that bypassed the SMS second
|
|
103
|
+
factor. The High #1 finding from the post-0.5.6 consistency audit.
|
|
104
|
+
|
|
105
|
+
Now, when the flag is `true` and the resolved user has SMS MFA set
|
|
106
|
+
up (`is_mfa_enabled=True` plus a `phone_number`):
|
|
107
|
+
|
|
108
|
+
- The OAuth callback sends the SMS code and stashes a short-lived
|
|
109
|
+
`login_mfa` pending JWT in the state row (instead of a session
|
|
110
|
+
token).
|
|
111
|
+
- `POST /oauth/exchange` returns `mfa_required=True` and
|
|
112
|
+
`mfa_pending_token=...` (with no `access_token`) so the SPA knows
|
|
113
|
+
to redirect to `/account/mfa-confirm`.
|
|
114
|
+
- The SPA's bundled `regstack.js` `oauth-complete` handler stashes
|
|
115
|
+
the pending token under `regstack.mfa_pending` (same key the
|
|
116
|
+
password-login MFA flow uses) and redirects.
|
|
117
|
+
- The user enters the SMS code and hits the existing
|
|
118
|
+
`POST /login/mfa-confirm` endpoint — same downstream path as the
|
|
119
|
+
password-login second factor.
|
|
120
|
+
|
|
121
|
+
Link flows (`mode="link"`) are exempt: the user was already
|
|
122
|
+
authenticated when they kicked off the link, so adding SMS friction
|
|
123
|
+
on top of an already-authenticated link operation has no
|
|
124
|
+
threat-model win.
|
|
125
|
+
|
|
126
|
+
The `ExchangeResponse` model grew two optional fields
|
|
127
|
+
(`mfa_required: bool = False`, `mfa_pending_token: str | None = None`)
|
|
128
|
+
and `access_token` is now defaulted to `""` so the MFA branch can
|
|
129
|
+
return cleanly. Existing handlers reading `access_token` keep
|
|
130
|
+
working — they just need to check `mfa_required` first.
|
|
131
|
+
|
|
132
|
+
## 0.5.8 — 2026-05-13
|
|
133
|
+
|
|
134
|
+
Audit-driven consistency cleanup — small fixes across the API surface
|
|
135
|
+
flagged by the post-0.5.6 consistency review.
|
|
136
|
+
|
|
137
|
+
**Security**
|
|
138
|
+
|
|
139
|
+
- **`oauth.completion_ttl_seconds` is finally enforced.** The flag has
|
|
140
|
+
been on `OAuthConfig` since the OAuth router shipped, but the
|
|
141
|
+
callback never used it: a state row stayed valid for the full
|
|
142
|
+
`state_ttl_seconds` (300s default) between callback completion and
|
|
143
|
+
`/oauth/exchange`. Now `set_result_token(...)` bumps the row's
|
|
144
|
+
expiry down to `now + completion_ttl_seconds` (30s default), so the
|
|
145
|
+
blast radius of a stolen state_id post-callback is the documented
|
|
146
|
+
30-second window. `OAuthStateRepoProtocol.set_result_token` grew an
|
|
147
|
+
optional `new_expires_at=` kwarg to make this atomic with the
|
|
148
|
+
token write.
|
|
149
|
+
|
|
150
|
+
**Changed (UserPublic surface)**
|
|
151
|
+
|
|
152
|
+
- `UserPublic` now serialises `updated_at` and
|
|
153
|
+
`tokens_invalidated_after`. SPAs comparing the latter against their
|
|
154
|
+
cached session JWT's `iat` can detect a forced sign-out after a
|
|
155
|
+
password / email change without an extra round-trip.
|
|
156
|
+
|
|
157
|
+
**Changed (hook payloads)**
|
|
158
|
+
|
|
159
|
+
- `oauth_signin_started` in `mode="link"` now carries the
|
|
160
|
+
authenticated `user=` kwarg, matching `oauth_signin_completed` and
|
|
161
|
+
`oauth_account_linked`. The `mode="signin"` call site stays without
|
|
162
|
+
`user=` (there isn't one yet — sign-in is what produces it).
|
|
163
|
+
|
|
164
|
+
**Internal**
|
|
165
|
+
|
|
166
|
+
- `OAuthConfig.completion_ttl_seconds` config field is now load-bearing
|
|
167
|
+
(was previously declared-but-unread).
|
|
168
|
+
- `MessageResponse` in `routers/oauth.py` deleted; the router now uses
|
|
169
|
+
the shared one from `routers/_schemas.py`. OpenAPI no longer carries
|
|
170
|
+
two identically-named schemas.
|
|
171
|
+
- `MongoOAuthStateRepo` / `SqlOAuthStateRepo` `set_result_token` grew
|
|
172
|
+
a `new_expires_at` parameter (default `None`, so existing callers
|
|
173
|
+
see no change).
|
|
174
|
+
- `MongoBlacklistRepo.purge_expired` switched from `$lte` to `$lt` to
|
|
175
|
+
match the rest of the `purge_expired` family across both backends.
|
|
176
|
+
Edge-instant tokens get one more microsecond of life — the
|
|
177
|
+
bulk-revoke check (which DOES use `<=`) is unchanged.
|
|
178
|
+
- Dead `create()` and `delete_by_id()` methods removed from
|
|
179
|
+
`MongoPendingRepo` — neither was in the protocol or the SQL impl,
|
|
180
|
+
and nothing in src or tests called them.
|
|
181
|
+
- OAuth `start` and `callback` endpoints now declare
|
|
182
|
+
`response_class=RedirectResponse` and `status_code=302`. OpenAPI
|
|
183
|
+
surfaces the redirect intent properly.
|
|
184
|
+
- Custom-claim JWT encoder in `routers/account.py` (email-change
|
|
185
|
+
token) now emits `iat` as a float instead of `int`, matching the
|
|
186
|
+
three other custom-claim encoders and the bulk-revoke contract.
|
|
187
|
+
- `routers/verify.py` `created_at` for resent pending registrations
|
|
188
|
+
now goes through `rs.clock.now()` instead of wall-clock
|
|
189
|
+
`datetime.now(UTC)` — keeps `FrozenClock`-driven tests
|
|
190
|
+
deterministic.
|
|
191
|
+
- `BaseUser.model_config = ConfigDict(extra="allow")` got a
|
|
192
|
+
comment explaining why it's the only model in the package that
|
|
193
|
+
doesn't `extra="forbid"`.
|
|
194
|
+
|
|
195
|
+
## 0.5.7 — 2026-05-13
|
|
196
|
+
|
|
197
|
+
Documentation-only follow-up to 0.5.6.
|
|
198
|
+
|
|
199
|
+
- `docs/configuration.md` now documents the per-route `*_rate_limit`
|
|
200
|
+
family (added in 0.5.4) instead of pointing at
|
|
201
|
+
`login_max_per_minute` / `login_max_per_hour` as reserved future
|
|
202
|
+
fields.
|
|
203
|
+
- `docs/security.md` no longer references
|
|
204
|
+
`PasswordHasher.needs_rehash` (removed in 0.5.6). Replacement
|
|
205
|
+
guidance points hosts at `pwdlib.PasswordHash.verify_and_update`.
|
|
206
|
+
- Root `CHANGELOG.md` backfilled with 0.4.0 and 0.5.0 entries so it
|
|
207
|
+
matches `docs/changelog.md`.
|
|
208
|
+
|
|
209
|
+
## 0.5.6 — 2026-05-13
|
|
210
|
+
|
|
211
|
+
Eleven days of security-review remediation, supply-chain hardening,
|
|
212
|
+
a full `mypy --strict` cleanup pass, and the per-route rate-limits
|
|
213
|
+
feature rolled up into a single release.
|
|
214
|
+
|
|
215
|
+
**Per-route IP rate limits.** Opt-in via the new `rate_limit` extra
|
|
216
|
+
(or a host-supplied `slowapi.Limiter`) plus any of the new
|
|
217
|
+
`RegStackConfig.*_rate_limit` fields (`login_rate_limit`,
|
|
218
|
+
`register_rate_limit`, `forgot_password_rate_limit`,
|
|
219
|
+
`reset_password_rate_limit`, `verify_rate_limit`,
|
|
220
|
+
`resend_verification_rate_limit`, `change_password_rate_limit`,
|
|
221
|
+
`change_email_rate_limit`, `confirm_email_change_rate_limit`,
|
|
222
|
+
`delete_account_rate_limit`). Each accepts a slowapi-syntax string
|
|
223
|
+
(`"5/minute"`, `"5/minute;20/hour"`). Empty / unset means no limit
|
|
224
|
+
on that route — `LockoutService` still defends `/login` against
|
|
225
|
+
credential stuffing per-account. When `*_rate_limit` strings are
|
|
226
|
+
configured but neither a `rate_limiter=` argument is passed nor
|
|
227
|
+
the `rate_limit` extra is installed, `RegStack.router` raises
|
|
228
|
+
`RuntimeError` on first access — failing closed beats silently
|
|
229
|
+
disabling the protection. Hosts remain responsible for
|
|
230
|
+
`app.state.limiter` and `app.add_exception_handler(RateLimitExceeded, ...)`;
|
|
231
|
+
slowapi owns the 429 response shape. The previously-reserved
|
|
232
|
+
`login_max_per_minute` / `login_max_per_hour` fields are kept for
|
|
233
|
+
back-compat but unwired.
|
|
234
|
+
|
|
235
|
+
**Security fixes.**
|
|
236
|
+
|
|
237
|
+
- JWT 401 detail now returns a static `"Invalid or expired token."`;
|
|
238
|
+
no longer leaks the pyjwt failure reason (signature mismatch /
|
|
239
|
+
expired / malformed / audience mismatch).
|
|
240
|
+
- OAuth sign-in now honours `allow_registration=False`. Previously,
|
|
241
|
+
`/register` respected the flag but the OAuth `_resolve_user`
|
|
242
|
+
"brand-new account" branch did not, creating accounts even when
|
|
243
|
+
self-service signup was disabled.
|
|
244
|
+
- Admin `DELETE /admin/users/{id}` now cascades `oauth_identities`,
|
|
245
|
+
matching the user-initiated `DELETE /account` path. Previously
|
|
246
|
+
left orphan rows that blocked re-registration of the same Google
|
|
247
|
+
subject.
|
|
248
|
+
- `POST /phone/start` and `DELETE /phone` now return 400 (not crash
|
|
249
|
+
with HTTP 500) for OAuth-only users who have no `hashed_password`.
|
|
250
|
+
|
|
251
|
+
**Breaking change — hook contracts.** `mfa_login_started` and
|
|
252
|
+
`phone_setup_started` no longer include the raw OTP code in their
|
|
253
|
+
kwargs. Hooks are best-effort observability and are the documented
|
|
254
|
+
integration surface for analytics / logging / Slack notifications,
|
|
255
|
+
so a plaintext OTP in `**kwargs` is a leak waiting to happen.
|
|
256
|
+
Hosts that subscribed to either event to take over SMS delivery
|
|
257
|
+
should migrate to a custom `SmsService` subclass — the supported
|
|
258
|
+
delivery override.
|
|
259
|
+
|
|
260
|
+
**Dependency floors raised for CVEs.**
|
|
261
|
+
|
|
262
|
+
- `pyjwt>=2.12.1` for CVE-2026-32597 (`crit` header bypass, CVSS 7.5).
|
|
263
|
+
- `cryptography>=46.0.7` added explicitly to the `oauth` extra for
|
|
264
|
+
CVE-2026-26007 (ECC subgroup attack on the JWKS code path, CVSS
|
|
265
|
+
8.2) plus CVE-2026-34073 and CVE-2026-39892.
|
|
266
|
+
- `python-multipart>=0.0.26` for CVE-2026-40347 (DoS via oversized
|
|
267
|
+
multipart preamble).
|
|
268
|
+
|
|
269
|
+
**Supply chain.** `pypa/gh-action-pypi-publish` in `publish.yml`
|
|
270
|
+
pinned to a commit SHA instead of the mutable `release/v1` branch.
|
|
271
|
+
The publish job holds `id-token: write`, so a tag/branch swap
|
|
272
|
+
upstream would let an attacker push a malicious wheel under our
|
|
273
|
+
OIDC identity.
|
|
274
|
+
|
|
275
|
+
**Removed.** `PasswordHasher.needs_rehash` — called pwdlib's
|
|
276
|
+
non-existent `check_needs_rehash` and would `AttributeError` if
|
|
277
|
+
anyone invoked it. No callers in src or tests. If you were planning
|
|
278
|
+
to use it, call `pwdlib.PasswordHash.verify_and_update` directly.
|
|
279
|
+
|
|
280
|
+
**Internal.** 72 `mypy --strict` errors cleared across 35 files;
|
|
281
|
+
`inv lint` is now green end-to-end. Mongo
|
|
282
|
+
`BlacklistRepo.purge_expired` added (protocol parity with SQL).
|
|
283
|
+
`KNOWN_EVENTS` reconciled — 7 previously-undeclared events added
|
|
284
|
+
(`verification_requested`, `email_change_requested`, `email_changed`,
|
|
285
|
+
`phone_setup_started`, `mfa_login_started`, `mfa_enabled`,
|
|
286
|
+
`mfa_disabled`). `user_logged_out` now actually fires from
|
|
287
|
+
`routers/logout.py` (was listed in `KNOWN_EVENTS` but no router
|
|
288
|
+
emitted it).
|
|
289
|
+
|
|
290
|
+
## 0.5.0 — 2026-05-02
|
|
291
|
+
|
|
292
|
+
**Theme designer.** `regstack theme design` opens a native pywebview
|
|
293
|
+
window with controls for every `--rs-*` CSS custom property and a
|
|
294
|
+
real-time preview of the bundled SSR widgets (sign-in form, success /
|
|
295
|
+
error banners, danger-zone button). Saving writes `regstack-theme.css`;
|
|
296
|
+
the designer round-trips values back into the form on next launch so
|
|
297
|
+
iteration is non-destructive. `--print-only` mode takes repeatable
|
|
298
|
+
`--var NAME=VALUE` pairs (with a `dark:` prefix for dark-scheme
|
|
299
|
+
overrides) and writes the file headlessly. Lives in
|
|
300
|
+
`regstack.wizard.theme_designer`; registered as a lazy Click subgroup
|
|
301
|
+
so `regstack init` / `doctor` don't pay the pywebview/uvicorn import
|
|
302
|
+
cost.
|
|
303
|
+
|
|
304
|
+
**Docs.** New "About the examples" convention block at the top of
|
|
305
|
+
`docs/index.md`. Every URL, email, smtp host, and admin command across
|
|
306
|
+
the docs now extrapolates from the same fictional app at
|
|
307
|
+
`app.example.com` with `<username>` / `<password>` placeholders.
|
|
308
|
+
|
|
309
|
+
## 0.4.0 — 2026-05-02
|
|
310
|
+
|
|
311
|
+
**OAuth setup wizard.** `regstack oauth setup` opens a native webview
|
|
312
|
+
window that walks an operator through registering a Google OAuth 2.0
|
|
313
|
+
client and merges the credentials into `regstack.toml` +
|
|
314
|
+
`regstack.secrets.env` non-destructively (preserves comments, other
|
|
315
|
+
tables, unrelated keys). 12-step SPA inside a local-only 127.0.0.1
|
|
316
|
+
FastAPI server, gated by a per-launch random token. Each "Next" click
|
|
317
|
+
hits a server-side validator so the Write step can never be reached
|
|
318
|
+
with bad data. `--print-only` mode skips the GUI for headless / CI
|
|
319
|
+
use.
|
|
320
|
+
|
|
321
|
+
Three new base dependencies — `pywebview>=5.0`, `tomlkit>=0.13`,
|
|
322
|
+
`uvicorn[standard]>=0.29` — for the wizard's local server.
|
|
323
|
+
`pytest-playwright` added to the `dev` extra; new `inv test-e2e` task
|
|
324
|
+
chained into `inv test-all`.
|
|
325
|
+
|
|
326
|
+
## 0.3.0 — 2026-04-30
|
|
327
|
+
|
|
328
|
+
**OAuth — Sign in with Google.** Opt-in via the new `oauth` extra
|
|
329
|
+
and `enable_oauth=True`. Five JSON endpoints, an SSR
|
|
330
|
+
`/account/oauth-complete` page, "Sign in with Google" button on the
|
|
331
|
+
login page, and a Connected-accounts panel on `/account/me`.
|
|
332
|
+
|
|
333
|
+
Schema migration `0002_oauth.py` creates `oauth_identities` +
|
|
334
|
+
`oauth_states` and makes `users.hashed_password` nullable
|
|
335
|
+
(OAuth-only users have no password). Roll forward via
|
|
336
|
+
`regstack migrate` or first-boot `install_schema()` — no manual
|
|
337
|
+
intervention.
|
|
338
|
+
|
|
339
|
+
Account-linking policy defaults to **refuse**: if a Google sign-in
|
|
340
|
+
arrives carrying an email that already belongs to a password-
|
|
341
|
+
registered user, the callback returns `?error=email_in_use` and the
|
|
342
|
+
user must sign in then explicitly link from `/account/me`. Hosts
|
|
343
|
+
that consciously accept the email-recycling threat for UX can flip
|
|
344
|
+
`oauth.auto_link_verified_emails = true`. See
|
|
345
|
+
[`docs/oauth.md`](https://regstack.readthedocs.io/en/latest/oauth.html)
|
|
346
|
+
and [`tasks/oauth-design.md`](https://github.com/jdrumgoole/regstack/blob/main/tasks/oauth-design.md)
|
|
347
|
+
for the full threat model.
|
|
348
|
+
|
|
349
|
+
**Migration**
|
|
350
|
+
|
|
351
|
+
- Install the new extra: `uv add 'regstack[oauth]'`.
|
|
352
|
+
- Set `enable_oauth = true` and provide `oauth.google_client_id` +
|
|
353
|
+
`oauth.google_client_secret`.
|
|
354
|
+
- Run `regstack migrate` (SQL backends only) or rely on
|
|
355
|
+
`install_schema()` at first boot.
|
|
356
|
+
|
|
357
|
+
`BaseUser.hashed_password` is now `str | None`. Code that imported
|
|
358
|
+
the field type explicitly will need to widen it.
|
|
359
|
+
|
|
360
|
+
## 0.2.6 — 2026-04-28
|
|
361
|
+
|
|
362
|
+
Bug fix.
|
|
363
|
+
|
|
364
|
+
- **Fix:** `/admin/stats` reported `pending_registrations: 0` on
|
|
365
|
+
every SQL backend. The route reached into the Mongo repo's private
|
|
366
|
+
`_collection` attribute and silently fell back to `0` when the
|
|
367
|
+
attribute was absent. Added `count_unexpired(now=None)` to
|
|
368
|
+
`PendingRepoProtocol` with Mongo + SQL implementations and routed
|
|
369
|
+
through `rs.clock.now()` so the count respects the injected clock.
|
|
370
|
+
New parametrized integration test exercises the count on every
|
|
371
|
+
backend.
|
|
372
|
+
|
|
373
|
+
## 0.2.5 — 2026-04-28
|
|
374
|
+
|
|
375
|
+
Bug fix + tooling.
|
|
376
|
+
|
|
377
|
+
- **Fix:** `regstack doctor` against a SQL backend crashed with
|
|
378
|
+
`asyncio.run() cannot be called from a running event loop`. The
|
|
379
|
+
schema check called `regstack.backends.sql.migrations.current()`,
|
|
380
|
+
which used `asyncio.run()` internally — invalid inside doctor's own
|
|
381
|
+
`asyncio.run`. Added `current_async()` and switched the doctor
|
|
382
|
+
command to use it. Sync `current()` is preserved for the migrate
|
|
383
|
+
CLI.
|
|
384
|
+
- **New:** `inv coverage [--no-html] [--fail-under=N]` runs the full
|
|
385
|
+
three-backend matrix under coverage and writes term + HTML reports.
|
|
386
|
+
Branch coverage is on by default.
|
|
387
|
+
- Test coverage uplift on the CLI: `cli/init.py` 14% → 88%,
|
|
388
|
+
`cli/doctor.py` 61% → 87%. Total: **85% → 87.1%**.
|
|
389
|
+
|
|
390
|
+
## 0.2.4 — 2026-04-28
|
|
391
|
+
|
|
392
|
+
**Breaking** — back-compat shims removed:
|
|
393
|
+
|
|
394
|
+
- `RegStack.install_indexes()` (alias for `install_schema()`).
|
|
395
|
+
- `ObjectIdStr` alias for `IdStr` in `regstack.models._objectid`.
|
|
396
|
+
- Re-exports of `UserAlreadyExistsError`,
|
|
397
|
+
`PendingAlreadyExistsError`, `MfaVerifyOutcome`, and
|
|
398
|
+
`MfaVerifyResult` from `regstack.backends.mongo.repositories.*`.
|
|
399
|
+
Their canonical home is `regstack.backends.protocols`.
|
|
400
|
+
|
|
401
|
+
If you import any of these from the old paths, switch to:
|
|
402
|
+
- `RegStack.install_schema()`
|
|
403
|
+
- `from regstack.models._objectid import IdStr`
|
|
404
|
+
- `from regstack.backends.protocols import UserAlreadyExistsError`
|
|
405
|
+
(and friends).
|
|
406
|
+
|
|
407
|
+
The internal mongo `install_indexes(db, config)` function is unchanged.
|
|
408
|
+
|
|
409
|
+
## 0.2.3 — 2026-04-28
|
|
410
|
+
|
|
411
|
+
Docs-only release. Restructured the API reference around the current
|
|
412
|
+
package layout (post multi-backend refactor) and added Google-style
|
|
413
|
+
docstrings (Args / Returns / Raises) to the public surface — RegStack,
|
|
414
|
+
JwtCodec, PasswordHasher, LockoutService, AuthDependencies,
|
|
415
|
+
HookRegistry, EmailService, SmsService, the router builders, and the
|
|
416
|
+
Clock implementations. Dataclass field docs moved to PEP 258
|
|
417
|
+
attribute docstrings. Sphinx builds clean under `-W` again.
|
|
418
|
+
|
|
419
|
+
## 0.2.2 — 2026-04-28
|
|
420
|
+
|
|
421
|
+
Docs-only release. The README and Sphinx docs landing page now lead
|
|
422
|
+
with the same pitch (problem framing, "Why not just use…?" comparison
|
|
423
|
+
vs Auth0 / Clerk / Keycloak / fastapi-users) before diving into
|
|
424
|
+
architecture. Hyperlink density trimmed back: only major external
|
|
425
|
+
packages, products, and JWT (RFC 7519) are linked — Wikipedia trivia,
|
|
426
|
+
MDN basics, OWASP article links, and deep-dependency helper-class
|
|
427
|
+
docs were removed.
|
|
428
|
+
|
|
429
|
+
## 0.2.1 — 2026-04-28
|
|
430
|
+
|
|
431
|
+
Hotfix for 0.2.0: `import regstack` failed on a base install because
|
|
432
|
+
several modules in the import path (`models/_objectid.py`,
|
|
433
|
+
`backends/protocols.py`, four routers, and the SQL `mfa_code_repo`)
|
|
434
|
+
had unconditional `from bson …` / `from regstack.backends.mongo …`
|
|
435
|
+
imports — but `pymongo` became an optional `mongo` extra in 0.2.0.
|
|
436
|
+
Added a CI smoketest that builds the wheel and imports it in a
|
|
437
|
+
no-extras venv, plus an in-process regression test that blocks `bson`
|
|
438
|
+
/ `pymongo` via `sys.meta_path`.
|
|
439
|
+
|
|
440
|
+
## 0.2.0 — 2026-04-28
|
|
441
|
+
|
|
442
|
+
Multi-backend support — SQLite (default), Postgres, MongoDB — switched
|
|
443
|
+
by `database_url` URL scheme. Bundled Alembic migrations for SQL
|
|
444
|
+
backends. Embedding API change: `RegStack(config=, db=)` →
|
|
445
|
+
`RegStack(config=, backend=None)`. README + core docs rewritten for
|
|
446
|
+
less-expert readers (problem framing, hyperlinks to external
|
|
447
|
+
standards, comparison vs Auth0/Clerk/Keycloak/fastapi-users).
|
|
448
|
+
|
|
449
|
+
See [`docs/changelog.md`](docs/changelog.md) for the full per-feature
|
|
450
|
+
breakdown.
|
|
451
|
+
|
|
452
|
+
## 0.1.1 — 2026-04-27
|
|
453
|
+
|
|
454
|
+
- Rewrite README relative links as absolute URLs so they resolve on the
|
|
455
|
+
PyPI project page. README-only release.
|
|
456
|
+
|
|
457
|
+
## 0.1.0 — 2026-04-27
|
|
458
|
+
|
|
459
|
+
First tagged release. Bundles M1–M6 from the development plan into a
|
|
460
|
+
single Apache-2.0 package on PyPI.
|
|
461
|
+
|
|
462
|
+
See [`docs/changelog.md`](docs/changelog.md) for the per-milestone
|
|
463
|
+
breakdown of M1 through M6.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -22,17 +22,14 @@ Requires-Dist: alembic>=1.13
|
|
|
22
22
|
Requires-Dist: click>=8.1
|
|
23
23
|
Requires-Dist: dnspython>=2.6
|
|
24
24
|
Requires-Dist: email-validator>=2.1
|
|
25
|
-
Requires-Dist: fastapi>=0.
|
|
26
|
-
Requires-Dist: jinja2>=3.1
|
|
25
|
+
Requires-Dist: fastapi>=0.120.0
|
|
26
|
+
Requires-Dist: jinja2>=3.1.6
|
|
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
30
|
Requires-Dist: pyjwt>=2.12.1
|
|
31
31
|
Requires-Dist: python-multipart>=0.0.26
|
|
32
|
-
Requires-Dist: pywebview>=5.0
|
|
33
32
|
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
34
|
-
Requires-Dist: tomlkit>=0.13
|
|
35
|
-
Requires-Dist: uvicorn[standard]>=0.29
|
|
36
33
|
Provides-Extra: dev
|
|
37
34
|
Requires-Dist: anyio>=4.3; extra == 'dev'
|
|
38
35
|
Requires-Dist: asyncpg>=0.29; extra == 'dev'
|
|
@@ -47,8 +44,10 @@ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
|
47
44
|
Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
|
|
48
45
|
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
|
|
49
46
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: pywebview>=5.0; extra == 'dev'
|
|
50
48
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
51
49
|
Requires-Dist: slowapi>=0.1.9; extra == 'dev'
|
|
50
|
+
Requires-Dist: tomlkit>=0.13; extra == 'dev'
|
|
52
51
|
Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
|
|
53
52
|
Provides-Extra: docs
|
|
54
53
|
Requires-Dist: furo>=2024.1; extra == 'docs'
|
|
@@ -72,6 +71,10 @@ Provides-Extra: sns
|
|
|
72
71
|
Requires-Dist: aioboto3>=12.3; extra == 'sns'
|
|
73
72
|
Provides-Extra: twilio
|
|
74
73
|
Requires-Dist: twilio>=9.0; extra == 'twilio'
|
|
74
|
+
Provides-Extra: wizard
|
|
75
|
+
Requires-Dist: pywebview>=5.0; extra == 'wizard'
|
|
76
|
+
Requires-Dist: tomlkit>=0.13; extra == 'wizard'
|
|
77
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == 'wizard'
|
|
75
78
|
Description-Content-Type: text/markdown
|
|
76
79
|
|
|
77
80
|
# regstack
|