regstack 0.3.0__tar.gz → 0.5.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.3.0 → regstack-0.5.0}/CLAUDE.md +31 -2
- {regstack-0.3.0 → regstack-0.5.0}/PKG-INFO +14 -7
- {regstack-0.3.0 → regstack-0.5.0}/README.md +9 -6
- {regstack-0.3.0 → regstack-0.5.0}/docs/api.md +73 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/architecture.md +48 -2
- {regstack-0.3.0 → regstack-0.5.0}/docs/changelog.md +45 -0
- regstack-0.5.0/docs/cli.md +187 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/conf.py +8 -1
- {regstack-0.3.0 → regstack-0.5.0}/docs/configuration.md +42 -8
- {regstack-0.3.0 → regstack-0.5.0}/docs/embedding.md +63 -8
- {regstack-0.3.0 → regstack-0.5.0}/docs/index.md +57 -2
- {regstack-0.3.0 → regstack-0.5.0}/docs/oauth.md +19 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/quickstart.md +1 -1
- regstack-0.5.0/docs/security-reports/README.md +15 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/security.md +71 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/theming.md +46 -1
- {regstack-0.3.0 → regstack-0.5.0}/pyproject.toml +15 -1
- regstack-0.5.0/scripts/security-review-prompt.md +583 -0
- regstack-0.5.0/src/regstack/cli/__main__.py +64 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/base.py +25 -27
- regstack-0.5.0/src/regstack/version.py +1 -0
- regstack-0.5.0/src/regstack/wizard/__init__.py +5 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/__init__.py +6 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/cli.py +224 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/routes.py +269 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/server.py +121 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/static/wizard.css +316 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/static/wizard.js +353 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/templates/wizard.html +260 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/validators.py +259 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/window.py +72 -0
- regstack-0.5.0/src/regstack/wizard/oauth_google/writer.py +248 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/__init__.py +8 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/cli.py +181 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/routes.py +278 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/server.py +87 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/static/designer.css +348 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/static/designer.js +272 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/templates/designer.html +89 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/validators.py +145 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/window.py +63 -0
- regstack-0.5.0/src/regstack/wizard/theme_designer/writer.py +196 -0
- {regstack-0.3.0 → regstack-0.5.0}/tasks.py +12 -1
- regstack-0.5.0/tests/e2e/conftest.py +121 -0
- regstack-0.5.0/tests/e2e/test_theme_designer.py +87 -0
- regstack-0.5.0/tests/e2e/test_wizard_oauth_flow.py +144 -0
- regstack-0.5.0/tests/unit/__init__.py +0 -0
- regstack-0.5.0/tests/unit/test_cli_migrate.py +151 -0
- regstack-0.5.0/tests/unit/test_theme_designer_cli.py +190 -0
- regstack-0.5.0/tests/unit/test_theme_designer_routes.py +179 -0
- regstack-0.5.0/tests/unit/test_theme_designer_validators.py +154 -0
- regstack-0.5.0/tests/unit/test_theme_designer_writer.py +156 -0
- regstack-0.5.0/tests/unit/test_wizard_oauth_cli.py +230 -0
- regstack-0.5.0/tests/unit/test_wizard_oauth_routes.py +232 -0
- regstack-0.5.0/tests/unit/test_wizard_oauth_validators.py +257 -0
- regstack-0.5.0/tests/unit/test_wizard_oauth_writer.py +293 -0
- {regstack-0.3.0 → regstack-0.5.0}/uv.lock +269 -1
- regstack-0.3.0/docs/cli.md +0 -87
- regstack-0.3.0/src/regstack/cli/__main__.py +0 -29
- regstack-0.3.0/src/regstack/version.py +0 -1
- {regstack-0.3.0 → regstack-0.5.0}/.github/workflows/publish.yml +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/.github/workflows/test.yml +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/.gitignore +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/.python-version +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/.readthedocs.yaml +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/CHANGELOG.md +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/LICENSE +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/NOTICE +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/SECURITY.md +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/_static/.gitkeep +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/docs/_templates/.gitkeep +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/_common/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/_common/app.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/README.md +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/branding/theme.css +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/main.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/mongo/regstack.toml +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/postgres/README.md +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/postgres/main.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/postgres/regstack.toml +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/sqlite/README.md +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/sqlite/main.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/examples/sqlite/regstack.toml +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/regstack.toml.example +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/app.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/clock.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/dependencies.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/lockout.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/password.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/base.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/factory.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/backend.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/client.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/indexes.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/oauth_identity_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/oauth_state_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/protocols.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/env.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/migrations/versions/0002_oauth.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/oauth_identity_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/oauth_state_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/backends/sql/types.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/admin.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/doctor.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/init.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/loader.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/schema.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/config/secrets.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/base.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/composer.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/console.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/factory.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/ses.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/smtp.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/hooks/events.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/_objectid.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/oauth_identity.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/oauth_state.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/models/user.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/errors.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/providers/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/providers/google.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/oauth/registry.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/account.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/admin.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/login.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/logout.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/oauth.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/password.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/phone.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/register.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/routers/verify.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/base.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/factory.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/null.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/sns.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/pages.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/static/js/regstack.js +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/oauth_complete.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tasks/oauth-design.md +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/_fake_google/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/_fake_google/provider.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/conftest.py +0 -0
- {regstack-0.3.0/tests/integration → regstack-0.5.0/tests/e2e}/__init__.py +0 -0
- {regstack-0.3.0/tests/unit → regstack-0.5.0/tests/integration}/__init__.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_account_management.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_admin_router.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_happy_path.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_indexes.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_mfa.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_oauth_google_router.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_oauth_repos.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_oauth_ui.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_sql_migrations.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/integration/test_verification.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_base_install_imports.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_cli.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_cli_doctor.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_cli_init.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_jwt.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_lockout.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_mfa_code_repo.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_oauth_google.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_password.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_sms.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_smtp_backend.py +0 -0
- {regstack-0.3.0 → regstack-0.5.0}/tests/unit/test_ui_env.py +0 -0
|
@@ -60,8 +60,37 @@ The full plan, including milestone scope and deferred items, lives at
|
|
|
60
60
|
Phone setup uses a separate signed `phone_setup` JWT carrying the
|
|
61
61
|
proposed phone as a custom claim — same per-purpose key derivation as
|
|
62
62
|
password-reset and email-change.
|
|
63
|
-
- **OAuth
|
|
64
|
-
|
|
63
|
+
- **OAuth — done (0.3.0).** `OAuthProvider` ABC + `OAuthRegistry`,
|
|
64
|
+
Google provider (Authorization Code with PKCE, ID-token verification
|
|
65
|
+
via `pyjwt[crypto]` + `PyJWKClient`), 5 JSON endpoints,
|
|
66
|
+
`/account/oauth-complete` SSR token-handoff page, "Sign in with
|
|
67
|
+
Google" button on `/account/login`, Connected-accounts panel on
|
|
68
|
+
`/account/me`. Optional extra: `oauth = ["pyjwt[crypto]>=2.8"]`.
|
|
69
|
+
- **OAuth setup wizard — done.** `regstack oauth setup` opens a native
|
|
70
|
+
pywebview window over a 127.0.0.1 FastAPI server (random port +
|
|
71
|
+
one-shot launch token). 12-step SPA, per-step server validation,
|
|
72
|
+
non-clobbering tomlkit merge into `regstack.toml` +
|
|
73
|
+
`regstack.secrets.env`. Lives in `src/regstack/wizard/oauth_google/`;
|
|
74
|
+
Click subcommand registered through a `_LazyOauthGroup` so
|
|
75
|
+
`regstack init` / `doctor` don't pay the pywebview/uvicorn import
|
|
76
|
+
cost. `--print-only` mode runs the same merge headlessly. Tested at
|
|
77
|
+
four layers: validators (unit), writer (golden-file), routes
|
|
78
|
+
(TestClient), full SPA flow (Playwright e2e). Run e2e with
|
|
79
|
+
`inv test-e2e`; `inv test-all` chains it after the backend matrix.
|
|
80
|
+
- **Theme designer — done.** `regstack theme design` opens a sibling
|
|
81
|
+
pywebview window with controls for every `--rs-*` variable and a
|
|
82
|
+
real-time preview of the bundled SSR widgets (renders the same
|
|
83
|
+
`.rs-*` classes the SSR pages render, so what you see is what
|
|
84
|
+
you'll ship). Save writes `regstack-theme.css`; the designer
|
|
85
|
+
round-trips values back into the form on next launch so iteration
|
|
86
|
+
is non-destructive. Lives in `src/regstack/wizard/theme_designer/`,
|
|
87
|
+
registered via a `_LazyThemeGroup` mirroring the OAuth pattern.
|
|
88
|
+
Same four-layer test stack (validators / writer / routes /
|
|
89
|
+
Playwright e2e). The shared scaffold (FastAPI app, uvicorn launcher,
|
|
90
|
+
pywebview shim, e2e fixture pattern) is duplicated rather than
|
|
91
|
+
abstracted into a base class — the two tools have meaningfully
|
|
92
|
+
different inputs and an early shared base would calcify the wrong
|
|
93
|
+
decisions. Refactor only when a third pywebview tool lands.
|
|
65
94
|
|
|
66
95
|
## Three kinds of single-use proof
|
|
67
96
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -29,7 +29,10 @@ Requires-Dist: pydantic-settings>=2.2
|
|
|
29
29
|
Requires-Dist: pydantic>=2.6
|
|
30
30
|
Requires-Dist: pyjwt>=2.8
|
|
31
31
|
Requires-Dist: python-multipart>=0.0.9
|
|
32
|
+
Requires-Dist: pywebview>=5.0
|
|
32
33
|
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
34
|
+
Requires-Dist: tomlkit>=0.13
|
|
35
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
33
36
|
Provides-Extra: dev
|
|
34
37
|
Requires-Dist: anyio>=4.3; extra == 'dev'
|
|
35
38
|
Requires-Dist: asyncpg>=0.29; extra == 'dev'
|
|
@@ -40,6 +43,7 @@ Requires-Dist: pyjwt[crypto]>=2.8; extra == 'dev'
|
|
|
40
43
|
Requires-Dist: pymongo>=4.9; extra == 'dev'
|
|
41
44
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
42
45
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
|
|
43
47
|
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
|
|
44
48
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
45
49
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
@@ -78,9 +82,9 @@ auth bugs.**
|
|
|
78
82
|
|
|
79
83
|
`pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
|
|
80
84
|
or [MongoDB](https://www.mongodb.com/), and you have register / login /
|
|
81
|
-
verify-email / reset-password / change-email / delete-account /
|
|
82
|
-
two-factor / admin endpoints / themable
|
|
83
|
-
API and one config file.
|
|
85
|
+
verify-email / reset-password / change-email / delete-account / Sign in
|
|
86
|
+
with Google / optional SMS two-factor / admin endpoints / themable
|
|
87
|
+
HTML pages — all behind a small Python API and one config file.
|
|
84
88
|
|
|
85
89
|
📚 **Docs:** <https://regstack.readthedocs.io>
|
|
86
90
|
·
|
|
@@ -132,13 +136,14 @@ result everywhere is what regstack is for.
|
|
|
132
136
|
✔ Forgot / reset password — anti-enumeration: identical responses
|
|
133
137
|
✔ Change password (revokes old tokens) / change email (re-verify)
|
|
134
138
|
✔ Delete account
|
|
139
|
+
✔ Sign in with Google (PKCE + ID-token verification, opt-in)
|
|
135
140
|
✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
|
|
136
141
|
✔ Server-side login lockout (HTTP 429 + Retry-After)
|
|
137
142
|
✔ Admin endpoints (list / disable / delete users, stats)
|
|
138
143
|
✔ Server-rendered HTML pages, theme with one CSS file
|
|
139
144
|
✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
|
|
140
145
|
✔ Argon2 password hashing, CSP-friendly templates
|
|
141
|
-
✔ Setup
|
|
146
|
+
✔ Setup wizards (live in their own pywebview windows): `regstack init` (project bootstrap), `regstack oauth setup` (guided Google OAuth client config), `regstack theme design` (live theme designer with preview), and `regstack doctor` (config validator)
|
|
142
147
|
✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
|
|
143
148
|
```
|
|
144
149
|
|
|
@@ -181,7 +186,7 @@ register from the command line:
|
|
|
181
186
|
```bash
|
|
182
187
|
curl -X POST http://localhost:8000/api/auth/register \
|
|
183
188
|
-H 'content-type: application/json' \
|
|
184
|
-
-d '{"email":"alice@example.com","password":"
|
|
189
|
+
-d '{"email":"alice@app.example.com","password":"<password>","full_name":"Alice"}'
|
|
185
190
|
```
|
|
186
191
|
|
|
187
192
|
The bundled example serves themed SSR pages at `/account/*`, prints
|
|
@@ -245,7 +250,9 @@ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdr
|
|
|
245
250
|
|
|
246
251
|
Alpha. Single-file SQLite is the default and runs with no infrastructure;
|
|
247
252
|
PostgreSQL and MongoDB backends pass the same parametrized integration
|
|
248
|
-
suite.
|
|
253
|
+
suite. OAuth (Google) shipped in `v0.3.0`; the `regstack oauth setup`
|
|
254
|
+
guided wizard in `v0.4.0`; the live `regstack theme design` tool in
|
|
255
|
+
`v0.5.0`. Latest tagged release: `v0.5.0`. See the
|
|
249
256
|
[changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
|
|
250
257
|
for the per-release breakdown.
|
|
251
258
|
|
|
@@ -11,9 +11,9 @@ auth bugs.**
|
|
|
11
11
|
|
|
12
12
|
`pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
|
|
13
13
|
or [MongoDB](https://www.mongodb.com/), and you have register / login /
|
|
14
|
-
verify-email / reset-password / change-email / delete-account /
|
|
15
|
-
two-factor / admin endpoints / themable
|
|
16
|
-
API and one config file.
|
|
14
|
+
verify-email / reset-password / change-email / delete-account / Sign in
|
|
15
|
+
with Google / optional SMS two-factor / admin endpoints / themable
|
|
16
|
+
HTML pages — all behind a small Python API and one config file.
|
|
17
17
|
|
|
18
18
|
📚 **Docs:** <https://regstack.readthedocs.io>
|
|
19
19
|
·
|
|
@@ -65,13 +65,14 @@ result everywhere is what regstack is for.
|
|
|
65
65
|
✔ Forgot / reset password — anti-enumeration: identical responses
|
|
66
66
|
✔ Change password (revokes old tokens) / change email (re-verify)
|
|
67
67
|
✔ Delete account
|
|
68
|
+
✔ Sign in with Google (PKCE + ID-token verification, opt-in)
|
|
68
69
|
✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
|
|
69
70
|
✔ Server-side login lockout (HTTP 429 + Retry-After)
|
|
70
71
|
✔ Admin endpoints (list / disable / delete users, stats)
|
|
71
72
|
✔ Server-rendered HTML pages, theme with one CSS file
|
|
72
73
|
✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
|
|
73
74
|
✔ Argon2 password hashing, CSP-friendly templates
|
|
74
|
-
✔ Setup
|
|
75
|
+
✔ Setup wizards (live in their own pywebview windows): `regstack init` (project bootstrap), `regstack oauth setup` (guided Google OAuth client config), `regstack theme design` (live theme designer with preview), and `regstack doctor` (config validator)
|
|
75
76
|
✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
|
|
76
77
|
```
|
|
77
78
|
|
|
@@ -114,7 +115,7 @@ register from the command line:
|
|
|
114
115
|
```bash
|
|
115
116
|
curl -X POST http://localhost:8000/api/auth/register \
|
|
116
117
|
-H 'content-type: application/json' \
|
|
117
|
-
-d '{"email":"alice@example.com","password":"
|
|
118
|
+
-d '{"email":"alice@app.example.com","password":"<password>","full_name":"Alice"}'
|
|
118
119
|
```
|
|
119
120
|
|
|
120
121
|
The bundled example serves themed SSR pages at `/account/*`, prints
|
|
@@ -178,7 +179,9 @@ The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdr
|
|
|
178
179
|
|
|
179
180
|
Alpha. Single-file SQLite is the default and runs with no infrastructure;
|
|
180
181
|
PostgreSQL and MongoDB backends pass the same parametrized integration
|
|
181
|
-
suite.
|
|
182
|
+
suite. OAuth (Google) shipped in `v0.3.0`; the `regstack oauth setup`
|
|
183
|
+
guided wizard in `v0.4.0`; the live `regstack theme design` tool in
|
|
184
|
+
`v0.5.0`. Latest tagged release: `v0.5.0`. See the
|
|
182
185
|
[changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
|
|
183
186
|
for the per-release breakdown.
|
|
184
187
|
|
|
@@ -18,6 +18,7 @@ The handful of things you import from `regstack` directly:
|
|
|
18
18
|
- [`RegStackConfig`](#regstack.config.schema.RegStackConfig) — top-level config.
|
|
19
19
|
- [`EmailConfig`](#regstack.config.schema.EmailConfig) — email-backend sub-config.
|
|
20
20
|
- [`SmsConfig`](#regstack.config.schema.SmsConfig) — SMS-backend sub-config.
|
|
21
|
+
- [`OAuthConfig`](#regstack.config.schema.OAuthConfig) — OAuth provider sub-config.
|
|
21
22
|
|
|
22
23
|
Most embeddings need only `RegStack` and `RegStackConfig`.
|
|
23
24
|
|
|
@@ -59,6 +60,11 @@ default.
|
|
|
59
60
|
:show-inheritance:
|
|
60
61
|
:exclude-members: model_config, model_fields, model_computed_fields
|
|
61
62
|
|
|
63
|
+
.. autoclass:: regstack.config.schema.OAuthConfig
|
|
64
|
+
:members:
|
|
65
|
+
:show-inheritance:
|
|
66
|
+
:exclude-members: model_config, model_fields, model_computed_fields
|
|
67
|
+
|
|
62
68
|
.. autofunction:: regstack.config.loader.load_config
|
|
63
69
|
```
|
|
64
70
|
|
|
@@ -247,6 +253,73 @@ MessageBird, …) and pass the instance to `regstack.set_email_backend`
|
|
|
247
253
|
.. autofunction:: regstack.sms.factory.build_sms_service
|
|
248
254
|
```
|
|
249
255
|
|
|
256
|
+
## OAuth
|
|
257
|
+
|
|
258
|
+
Opt-in subsystem behind `enable_oauth` and the `oauth` extra. v1
|
|
259
|
+
ships Google; the abstraction is shaped so adding GitHub /
|
|
260
|
+
Microsoft / Apple later is a new module under
|
|
261
|
+
`regstack.oauth.providers` plus a registry entry. The full host
|
|
262
|
+
guide is in [OAuth](oauth.md); the threat model is in
|
|
263
|
+
[Security model](security.md#oauth-sign-in-with-google).
|
|
264
|
+
|
|
265
|
+
### Provider abstraction
|
|
266
|
+
|
|
267
|
+
```{eval-rst}
|
|
268
|
+
.. autoclass:: regstack.oauth.base.OAuthProvider
|
|
269
|
+
:members:
|
|
270
|
+
|
|
271
|
+
.. autoclass:: regstack.oauth.base.OAuthTokens
|
|
272
|
+
:members:
|
|
273
|
+
|
|
274
|
+
.. autoclass:: regstack.oauth.base.OAuthUserInfo
|
|
275
|
+
:members:
|
|
276
|
+
|
|
277
|
+
.. autoclass:: regstack.oauth.registry.OAuthRegistry
|
|
278
|
+
:members:
|
|
279
|
+
|
|
280
|
+
.. autoexception:: regstack.oauth.errors.OAuthError
|
|
281
|
+
|
|
282
|
+
.. autoexception:: regstack.oauth.errors.OAuthConfigError
|
|
283
|
+
|
|
284
|
+
.. autoexception:: regstack.oauth.errors.OAuthTokenExchangeError
|
|
285
|
+
|
|
286
|
+
.. autoexception:: regstack.oauth.errors.OAuthIdTokenError
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Google provider
|
|
290
|
+
|
|
291
|
+
```{eval-rst}
|
|
292
|
+
.. autoclass:: regstack.oauth.providers.google.GoogleProvider
|
|
293
|
+
:members:
|
|
294
|
+
:show-inheritance:
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Identity + state storage
|
|
298
|
+
|
|
299
|
+
```{eval-rst}
|
|
300
|
+
.. autoclass:: regstack.models.oauth_identity.OAuthIdentity
|
|
301
|
+
:members:
|
|
302
|
+
:exclude-members: model_config, model_fields, model_computed_fields
|
|
303
|
+
|
|
304
|
+
.. autoclass:: regstack.models.oauth_state.OAuthState
|
|
305
|
+
:members:
|
|
306
|
+
:exclude-members: model_config, model_fields, model_computed_fields
|
|
307
|
+
|
|
308
|
+
.. autoclass:: regstack.backends.protocols.OAuthIdentityRepoProtocol
|
|
309
|
+
:members:
|
|
310
|
+
|
|
311
|
+
.. autoclass:: regstack.backends.protocols.OAuthStateRepoProtocol
|
|
312
|
+
:members:
|
|
313
|
+
|
|
314
|
+
.. autoexception:: regstack.backends.protocols.OAuthIdentityAlreadyLinkedError
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Router
|
|
318
|
+
|
|
319
|
+
```{eval-rst}
|
|
320
|
+
.. autofunction:: regstack.routers.oauth.build_oauth_router
|
|
321
|
+
```
|
|
322
|
+
|
|
250
323
|
## Hooks
|
|
251
324
|
|
|
252
325
|
The event bus regstack uses to fire side-effect notifications
|
|
@@ -139,8 +139,52 @@ The composite `router` conditionally includes:
|
|
|
139
139
|
- `password` (forgot/reset) — when `enable_password_reset`.
|
|
140
140
|
- `phone` and the `mfa-confirm` route — when `enable_sms_2fa`.
|
|
141
141
|
- `admin` — when `enable_admin_router`.
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
- `oauth` — when `enable_oauth` AND at least one provider is
|
|
143
|
+
registered on `rs.oauth`.
|
|
144
|
+
|
|
145
|
+
`ui_router` mounts the same conditional pages, plus
|
|
146
|
+
`/account/oauth-complete` when `enable_oauth` is on.
|
|
147
|
+
|
|
148
|
+
## OAuth subsystem
|
|
149
|
+
|
|
150
|
+
Opt-in. Lives in `regstack.oauth/`; hosts pull it in via the
|
|
151
|
+
`oauth` extra (`pyjwt[crypto]>=2.8`). Imports are lazy — the
|
|
152
|
+
package keeps importing on a base install with no `cryptography`
|
|
153
|
+
installed, and the OAuth-specific modules only get loaded when
|
|
154
|
+
`enable_oauth` is on.
|
|
155
|
+
|
|
156
|
+
The shape is:
|
|
157
|
+
|
|
158
|
+
- `OAuthProvider` ABC — three methods: `authorization_url`,
|
|
159
|
+
`exchange_code`, `verify_id_token`.
|
|
160
|
+
- `OAuthRegistry` — name-keyed map of providers, scoped to one
|
|
161
|
+
`RegStack` instance. The `RegStack` constructor reads
|
|
162
|
+
`config.oauth` and registers `GoogleProvider` automatically when
|
|
163
|
+
`enable_oauth` and the credentials are set; hosts can also
|
|
164
|
+
register custom providers post-construction.
|
|
165
|
+
- `GoogleProvider` — Authorization Code with PKCE, ID-token
|
|
166
|
+
verification via `pyjwt[crypto]` + `PyJWKClient` against Google's
|
|
167
|
+
JWKS. ~150 lines hand-rolled rather than pulling `authlib`.
|
|
168
|
+
- Two new repos via the protocol pattern:
|
|
169
|
+
`OAuthIdentityRepoProtocol` (links between regstack users and
|
|
170
|
+
external accounts; double-unique on `(provider, subject_id)` and
|
|
171
|
+
`(user_id, provider)`) and `OAuthStateRepoProtocol` (in-flight
|
|
172
|
+
state rows carrying the PKCE `code_verifier`, redirect target,
|
|
173
|
+
mode, and the `result_token` slot the SPA exchanges).
|
|
174
|
+
- `build_oauth_router(rs)` — the router with the five endpoints
|
|
175
|
+
(`/start`, `/callback`, `/exchange`, `/link/start`, `/link`) plus
|
|
176
|
+
`/oauth/providers` for the SSR connected-accounts panel.
|
|
177
|
+
|
|
178
|
+
The token-handoff round-trip avoids putting access tokens in URLs:
|
|
179
|
+
the callback stashes the freshly-minted session JWT on the state
|
|
180
|
+
row's `result_token`, redirects to `/account/oauth-complete?id=…`,
|
|
181
|
+
and the SPA POSTs that id back to `/oauth/exchange` to retrieve the
|
|
182
|
+
token. The exchange consumes the row atomically — the same id can't
|
|
183
|
+
be exchanged twice.
|
|
184
|
+
|
|
185
|
+
The full design (including the four-milestone build sequence and
|
|
186
|
+
the threat model) is in
|
|
187
|
+
[`tasks/oauth-design.md`](https://github.com/jdrumgoole/regstack/blob/main/tasks/oauth-design.md).
|
|
144
188
|
|
|
145
189
|
## Hooks
|
|
146
190
|
|
|
@@ -157,6 +201,8 @@ primary auth flow. Known events:
|
|
|
157
201
|
- `phone_setup_started` / `mfa_login_started`
|
|
158
202
|
- `mfa_enabled` / `mfa_disabled`
|
|
159
203
|
- `user_deleted`
|
|
204
|
+
- `oauth_signin_started` / `oauth_signin_completed`
|
|
205
|
+
- `oauth_account_linked` / `oauth_account_unlinked`
|
|
160
206
|
|
|
161
207
|
Hosts are free to subscribe to custom event names too — the registry
|
|
162
208
|
is just a `defaultdict(list)`. Use this surface to push events into
|
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Versions follow
|
|
4
4
|
[Semantic Versioning](https://semver.org/) once `1.0.0` ships.
|
|
5
5
|
|
|
6
|
+
## 0.5.0 — 2026-05-02
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **Theme designer.** `regstack theme design` opens a native pywebview
|
|
11
|
+
window with controls for every `--rs-*` CSS custom property and a
|
|
12
|
+
real-time preview of the bundled SSR widgets (sign-in form, success
|
|
13
|
+
/ error banners, danger-zone button). Saving writes
|
|
14
|
+
`regstack-theme.css`; the designer round-trips values back into the
|
|
15
|
+
form on next launch so iteration is non-destructive. `--print-only`
|
|
16
|
+
mode takes repeatable `--var NAME=VALUE` pairs (with a `dark:`
|
|
17
|
+
prefix for dark-scheme overrides) and writes the file headlessly.
|
|
18
|
+
Lives in `regstack.wizard.theme_designer`; registered as a lazy
|
|
19
|
+
Click subgroup so `regstack init` / `doctor` don't pay the
|
|
20
|
+
pywebview/uvicorn import cost.
|
|
21
|
+
- "Why use regstack" pitch in `docs/index.md` updated to surface the
|
|
22
|
+
two pywebview tools (`oauth setup` + `theme design`) as a
|
|
23
|
+
distinguishing feature vs. fastapi-users / Auth0 / Keycloak.
|
|
24
|
+
|
|
25
|
+
### Docs
|
|
26
|
+
|
|
27
|
+
- New "About the examples" convention block at the top of
|
|
28
|
+
`docs/index.md`. Every URL, email, smtp host, and admin command
|
|
29
|
+
across the docs now extrapolates from the same fictional app at
|
|
30
|
+
`app.example.com` with `<username>` / `<password>` placeholders —
|
|
31
|
+
no more `user:pw@host/dbname` / `db.internal/myapp` mishmash.
|
|
32
|
+
|
|
33
|
+
## 0.4.0 — 2026-05-02
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **OAuth setup wizard.** `regstack oauth setup` opens a native
|
|
38
|
+
webview window that walks an operator through registering a Google
|
|
39
|
+
OAuth 2.0 client and merges the credentials into `regstack.toml` +
|
|
40
|
+
`regstack.secrets.env` non-destructively (preserves comments, other
|
|
41
|
+
tables, unrelated keys). 12-step SPA inside a local-only
|
|
42
|
+
127.0.0.1 FastAPI server, gated by a per-launch random token. Each
|
|
43
|
+
Next click hits a server-side validator so the Write step can never
|
|
44
|
+
be reached with bad data. `--print-only` mode skips the GUI for
|
|
45
|
+
headless / CI use.
|
|
46
|
+
- Three new base dependencies: `pywebview>=5.0`, `tomlkit>=0.13`,
|
|
47
|
+
`uvicorn[standard]>=0.29` (the wizard's local server).
|
|
48
|
+
- `pytest-playwright` added to the `dev` extra; new `inv test-e2e`
|
|
49
|
+
task chained into `inv test-all`.
|
|
50
|
+
|
|
6
51
|
## 0.3.0 — 2026-04-30
|
|
7
52
|
|
|
8
53
|
**OAuth — Sign in with Google.** Built across four PRs (M1–M4 of
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
`regstack` is the entry point installed by the package. All sub-commands
|
|
4
|
+
share a config-loading model: programmatic kwargs > env vars >
|
|
5
|
+
`regstack.secrets.env` > `regstack.toml` > defaults. The `--config <path>`
|
|
6
|
+
flag overrides where the TOML file is found.
|
|
7
|
+
|
|
8
|
+
## `regstack init`
|
|
9
|
+
|
|
10
|
+
Interactive wizard that writes `regstack.toml` and `regstack.secrets.env`
|
|
11
|
+
in the current directory. Asks which backend to use (SQLite default →
|
|
12
|
+
Postgres → MongoDB), generates a 64-byte JWT secret, runs DNS sanity
|
|
13
|
+
checks if asked, never provisions infrastructure.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv run regstack init
|
|
17
|
+
uv run regstack init --target /etc/app --force
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
|
|
22
|
+
- `--target DIR` — directory to write the config files (default cwd).
|
|
23
|
+
- `--force` — overwrite without confirming.
|
|
24
|
+
|
|
25
|
+
Re-running the wizard prompts before overwriting unless `--force` is
|
|
26
|
+
passed; pre-existing answers aren't kept (the wizard is intentionally
|
|
27
|
+
stateless).
|
|
28
|
+
|
|
29
|
+
## `regstack oauth setup`
|
|
30
|
+
|
|
31
|
+
Opens a guided 12-step wizard in a native webview window that walks you
|
|
32
|
+
through registering a Google OAuth 2.0 client (project selection,
|
|
33
|
+
consent screen, redirect URI, credentials) and merges the result into
|
|
34
|
+
your existing `regstack.toml` and `regstack.secrets.env`. The merge is
|
|
35
|
+
**non-clobbering** — comments, unrelated tables (`[email]`, `[sms]`,
|
|
36
|
+
…), and unrelated top-level keys are preserved. Re-run any time to
|
|
37
|
+
rotate credentials or change the linking policy.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv run regstack oauth setup
|
|
41
|
+
uv run regstack oauth setup --target /etc/app
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
|
|
46
|
+
- `--target DIR` — directory containing (or to receive) `regstack.toml`
|
|
47
|
+
(default cwd).
|
|
48
|
+
- `--api-prefix PREFIX` — router prefix the host mounts regstack under
|
|
49
|
+
(default `/api/auth`). Used to compute the suggested redirect URI.
|
|
50
|
+
- `--port N` — pin the wizard server's TCP port (default: random free
|
|
51
|
+
port on `127.0.0.1`).
|
|
52
|
+
- `--print-only` — skip the GUI; print the TOML + secrets diff that
|
|
53
|
+
*would* be written to stdout, then exit. Useful for headless hosts
|
|
54
|
+
(CI, servers without a webview backend) and dry-run smoke tests.
|
|
55
|
+
Pair with `--client-id`, `--client-secret`, `--base-url`,
|
|
56
|
+
`--auto-link/--no-auto-link`, `--mfa/--no-mfa`.
|
|
57
|
+
|
|
58
|
+
The interactive mode requires a desktop environment with a webview
|
|
59
|
+
backend (WebKit on macOS, GTK / QtWebEngine on Linux, Edge WebView2 on
|
|
60
|
+
Windows). On a headless host it exits with a clear error pointing at
|
|
61
|
+
`--print-only`.
|
|
62
|
+
|
|
63
|
+
The wizard binds to `127.0.0.1` only and authenticates every API call
|
|
64
|
+
with a per-launch random token, so a hostile process on the same host
|
|
65
|
+
can't drive the write endpoint.
|
|
66
|
+
|
|
67
|
+
## `regstack theme design`
|
|
68
|
+
|
|
69
|
+
Opens a live designer for `regstack-theme.css` in a native pywebview
|
|
70
|
+
window. The left pane has controls for every `--rs-*` CSS custom
|
|
71
|
+
property; the right pane renders the bundled SSR widgets (sign-in
|
|
72
|
+
form, success / error messages, danger-zone button) with your changes
|
|
73
|
+
applied in real time. Click **Save** to write the file; **Reset to
|
|
74
|
+
defaults** to start over; **Copy CSS** to put the generated
|
|
75
|
+
stylesheet on the clipboard without writing.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv run regstack theme design
|
|
79
|
+
uv run regstack theme design --target /var/www/static
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
|
|
84
|
+
- `--target DIR` — directory to write `regstack-theme.css` into
|
|
85
|
+
(default cwd).
|
|
86
|
+
- `--filename NAME` — output filename
|
|
87
|
+
(default `regstack-theme.css`).
|
|
88
|
+
- `--port N` — pin the designer's TCP port (default: random free
|
|
89
|
+
port on `127.0.0.1`).
|
|
90
|
+
- `--print-only` — skip the GUI; write the file from `--var` pairs
|
|
91
|
+
and emit a JSON summary. For headless / CI use.
|
|
92
|
+
- `--var NAME=VALUE` — repeatable. Used with `--print-only`. Prefix
|
|
93
|
+
with `dark:` to set the dark-scheme value, e.g.
|
|
94
|
+
`--var dark:--rs-accent=#2dd4bf`.
|
|
95
|
+
|
|
96
|
+
Re-running the designer reloads the previous values out of the file,
|
|
97
|
+
so iterating on a theme is non-destructive. Only the `:root` and
|
|
98
|
+
`@media (prefers-color-scheme: dark)` blocks are managed by the
|
|
99
|
+
designer — anything else in the file is left alone.
|
|
100
|
+
|
|
101
|
+
Same security shape as `regstack oauth setup`: binds `127.0.0.1`
|
|
102
|
+
only, every API call requires a per-launch random token.
|
|
103
|
+
|
|
104
|
+
## `regstack create-admin`
|
|
105
|
+
|
|
106
|
+
Create or promote a superuser. Idempotent.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv run regstack create-admin --email admin@app.example.com
|
|
110
|
+
uv run regstack create-admin --email admin@app.example.com --password 'long-strong-password'
|
|
111
|
+
uv run regstack create-admin --email admin@app.example.com --config /etc/app/regstack.toml
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
|
|
116
|
+
- `--email EMAIL` *(required)*.
|
|
117
|
+
- `--password PW` — if omitted, prompts (with confirmation).
|
|
118
|
+
- `--config PATH` — TOML file to load (default: env or cwd).
|
|
119
|
+
|
|
120
|
+
If the user already exists, the command sets `is_superuser=True` and
|
|
121
|
+
keeps the existing password. If they don't exist, it creates the user
|
|
122
|
+
with `is_active=True`, `is_verified=True`, `is_superuser=True` and the
|
|
123
|
+
provided password.
|
|
124
|
+
|
|
125
|
+
## `regstack migrate`
|
|
126
|
+
|
|
127
|
+
Runs the bundled Alembic migrations against the configured
|
|
128
|
+
`database_url`. Idempotent — re-running on a DB already at the target
|
|
129
|
+
revision is a no-op. Use this on SQL backends (SQLite / PostgreSQL)
|
|
130
|
+
to roll the schema forward to a new regstack release.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
uv run regstack migrate
|
|
134
|
+
uv run regstack migrate --target head
|
|
135
|
+
uv run regstack migrate --config /etc/app/regstack.toml --target 0001
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Options:
|
|
139
|
+
|
|
140
|
+
- `--config PATH` — TOML file to load (default: cwd / `$REGSTACK_CONFIG`).
|
|
141
|
+
- `--target REV` — revision to upgrade to (default `head`). Accepts
|
|
142
|
+
any Alembic revision spec: a revision id (`0001`), a relative step
|
|
143
|
+
(`+1`), or `head`.
|
|
144
|
+
|
|
145
|
+
Mongo backends are silently skipped: TTL indexes are installed by
|
|
146
|
+
`RegStack.install_schema()` on every app start, so there's no separate
|
|
147
|
+
migration story to run. Output prints the before / after revision and
|
|
148
|
+
exits non-zero if Alembic raises.
|
|
149
|
+
|
|
150
|
+
## `regstack doctor`
|
|
151
|
+
|
|
152
|
+
Runs read-only validation against the loaded config and reports each
|
|
153
|
+
check on a green/red line. Exit code is the number of failed checks —
|
|
154
|
+
suitable for use in container health checks.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
uv run regstack doctor
|
|
158
|
+
uv run regstack doctor --config /etc/app/regstack.toml
|
|
159
|
+
uv run regstack doctor --check-dns
|
|
160
|
+
uv run regstack doctor --send-test-email alice@app.example.com
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Options:
|
|
164
|
+
|
|
165
|
+
- `--config PATH` — TOML file to load.
|
|
166
|
+
- `--check-dns` — run SPF / DMARC / MX `dig`s on the sender domain.
|
|
167
|
+
Internet-dependent; off by default.
|
|
168
|
+
- `--send-test-email TO` — actually send a probe email through the
|
|
169
|
+
configured backend. Costs real money on SES; off by default.
|
|
170
|
+
|
|
171
|
+
Default checks:
|
|
172
|
+
|
|
173
|
+
| Check | Pass criterion |
|
|
174
|
+
|-------|----------------|
|
|
175
|
+
| `jwt secret` | Present, ≥ 32 chars |
|
|
176
|
+
| `backend` | `Backend.ping()` succeeds (works for any backend) |
|
|
177
|
+
| `schema` | Mongo: required indexes present. SQL: `users` table responds to `count(*)` |
|
|
178
|
+
| `email backend` | `build_email_service(config.email)` instantiates |
|
|
179
|
+
|
|
180
|
+
Optional checks:
|
|
181
|
+
|
|
182
|
+
| Check | Pass criterion |
|
|
183
|
+
|-------|----------------|
|
|
184
|
+
| `dns mx` | At least one MX record on the sender domain |
|
|
185
|
+
| `dns spf` | A TXT record containing `v=spf1` |
|
|
186
|
+
| `dns dmarc` | A TXT record at `_dmarc.<domain>` containing `v=DMARC1` |
|
|
187
|
+
| `email send` | The configured backend's `send()` returned without raising |
|
|
@@ -38,7 +38,14 @@ extensions = [
|
|
|
38
38
|
source_suffix = {".md": "markdown", ".rst": "restructuredtext"}
|
|
39
39
|
master_doc = "index"
|
|
40
40
|
templates_path = ["_templates"]
|
|
41
|
-
exclude_patterns = [
|
|
41
|
+
exclude_patterns = [
|
|
42
|
+
"_build",
|
|
43
|
+
"Thumbs.db",
|
|
44
|
+
".DS_Store",
|
|
45
|
+
# Daily security review reports — these are GitHub-rendered
|
|
46
|
+
# markdown, not part of the published Sphinx site.
|
|
47
|
+
"security-reports/**",
|
|
48
|
+
]
|
|
42
49
|
|
|
43
50
|
# Suppress noisy autodoc warnings on dynamically-typed pydantic helpers.
|
|
44
51
|
suppress_warnings = ["autodoc.import_object"]
|
|
@@ -63,6 +63,12 @@ addressed in env using a `__` separator: `REGSTACK_EMAIL__FROM_ADDRESS`.
|
|
|
63
63
|
* - `mfa_code_collection`
|
|
64
64
|
- `"mfa_codes"`
|
|
65
65
|
-
|
|
66
|
+
* - `oauth_identity_collection`
|
|
67
|
+
- `"oauth_identities"`
|
|
68
|
+
-
|
|
69
|
+
* - `oauth_state_collection`
|
|
70
|
+
- `"oauth_states"`
|
|
71
|
+
-
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
## Backends
|
|
@@ -79,15 +85,16 @@ regstack picks a backend at construction time from the URL scheme of
|
|
|
79
85
|
- Notes
|
|
80
86
|
|
|
81
87
|
* - SQLite
|
|
82
|
-
- `sqlite+aiosqlite:///./
|
|
88
|
+
- `sqlite+aiosqlite:///./dbname.db`
|
|
83
89
|
- Default. Bundled in the base install — no extras needed.
|
|
84
90
|
`:memory:` works too (per-test).
|
|
85
91
|
* - Postgres
|
|
86
|
-
- `postgresql+asyncpg
|
|
92
|
+
- `postgresql+asyncpg://<username>:<password>@dbhost.example.com:5432/dbname`
|
|
87
93
|
- Requires the `postgres` extra (pulls in `asyncpg`). The driver is
|
|
88
94
|
pinned to `+asyncpg` — sync drivers won't work.
|
|
89
95
|
* - MongoDB
|
|
90
|
-
- `mongodb
|
|
96
|
+
- `mongodb://<username>:<password>@dbhost.example.com:27017/dbname`
|
|
97
|
+
(or `mongodb+srv://<username>:<password>@app.abc123.mongodb.net/dbname`)
|
|
91
98
|
- Requires the `mongo` extra (pulls in `pymongo`). Database is taken
|
|
92
99
|
from the URL path; falls back to ``mongodb_database`` if absent.
|
|
93
100
|
```
|
|
@@ -165,7 +172,9 @@ The active backend exposes the same five repository protocols on
|
|
|
165
172
|
- Mounts `/phone/*` routes and gates the MFA second step in `/login`.
|
|
166
173
|
* - `enable_oauth`
|
|
167
174
|
- `false`
|
|
168
|
-
-
|
|
175
|
+
- Mounts `/oauth/*` routes when at least one provider is registered
|
|
176
|
+
(currently Google). Requires the ``oauth`` extra
|
|
177
|
+
(``pip install 'regstack[oauth]'``).
|
|
169
178
|
```
|
|
170
179
|
|
|
171
180
|
## Lockout (login)
|
|
@@ -226,14 +235,14 @@ The active backend exposes the same five repository protocols on
|
|
|
226
235
|
```toml
|
|
227
236
|
[email]
|
|
228
237
|
backend = "console" # console | smtp | ses
|
|
229
|
-
from_address = "noreply
|
|
230
|
-
from_name = "
|
|
238
|
+
from_address = "noreply@app.example.com"
|
|
239
|
+
from_name = "Example App"
|
|
231
240
|
|
|
232
241
|
# smtp
|
|
233
|
-
smtp_host = "smtp.example.com"
|
|
242
|
+
smtp_host = "smtp.app.example.com"
|
|
234
243
|
smtp_port = 587
|
|
235
244
|
smtp_starttls = true
|
|
236
|
-
smtp_username = "
|
|
245
|
+
smtp_username = "<username>"
|
|
237
246
|
# smtp_password is a SecretStr — set via REGSTACK_EMAIL__SMTP_PASSWORD
|
|
238
247
|
|
|
239
248
|
# ses
|
|
@@ -256,6 +265,31 @@ twilio_account_sid = "AC…"
|
|
|
256
265
|
# twilio_auth_token via REGSTACK_SMS__TWILIO_AUTH_TOKEN
|
|
257
266
|
```
|
|
258
267
|
|
|
268
|
+
`[oauth]` (`OAuthConfig`):
|
|
269
|
+
|
|
270
|
+
```toml
|
|
271
|
+
[oauth]
|
|
272
|
+
google_client_id = "12345.apps.googleusercontent.com"
|
|
273
|
+
# google_client_secret via REGSTACK_OAUTH__GOOGLE_CLIENT_SECRET
|
|
274
|
+
# google_redirect_uri = "https://your.app/api/auth/oauth/google/callback"
|
|
275
|
+
# (default: f"{base_url}{api_prefix}/oauth/google/callback")
|
|
276
|
+
|
|
277
|
+
# Account-linking policy. Off by default — see docs/oauth.md and
|
|
278
|
+
# docs/security.md for the threat model. On = a Google sign-in for an
|
|
279
|
+
# existing email-registered user is auto-linked when Google's
|
|
280
|
+
# email_verified=true. Hosts choosing on are accepting the email-
|
|
281
|
+
# recycling-at-the-provider risk in exchange for less friction.
|
|
282
|
+
auto_link_verified_emails = false
|
|
283
|
+
|
|
284
|
+
# When true, an OAuth sign-in for a user with SMS MFA enabled still
|
|
285
|
+
# goes through the second-factor step. Off by default — the OAuth
|
|
286
|
+
# provider already authenticated the human.
|
|
287
|
+
enforce_mfa_on_oauth_signin = false
|
|
288
|
+
|
|
289
|
+
state_ttl_seconds = 300 # in-flight state row lifetime
|
|
290
|
+
completion_ttl_seconds = 30 # /oauth/exchange window after callback
|
|
291
|
+
```
|
|
292
|
+
|
|
259
293
|
## SSR / theming
|
|
260
294
|
|
|
261
295
|
```{list-table}
|