regstack 0.8.1__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {regstack-0.8.1 → regstack-0.8.2}/PKG-INFO +1 -1
- {regstack-0.8.1 → regstack-0.8.2}/pyproject.toml +1 -1
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/_results.py +9 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/doctor.py +119 -4
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/config/schema.py +9 -5
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/oauth/providers/google.py +9 -1
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/sms/null.py +6 -5
- regstack-0.8.2/src/regstack/version.py +1 -0
- regstack-0.8.1/src/regstack/version.py +0 -1
- {regstack-0.8.1 → regstack-0.8.2}/.gitignore +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/CHANGELOG.md +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/LICENSE +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/NOTICE +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/README.md +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/SECURITY.md +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/regstack.toml.example +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/app.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/clock.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/dependencies.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/lockout.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/password.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/rate_limit.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/base.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/factory.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/backend.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/client.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/indexes.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/oauth_identity_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/oauth_state_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/pending_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/user_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/protocols.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/migrations/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/migrations/env.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/migrations/versions/0002_oauth.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/mfa_code_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/oauth_identity_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/oauth_state_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/types.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/__main__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/_paths.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/admin.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/init.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/capture.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/cli.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/http.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/logtail.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/account.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/cleanup.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/core_auth.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/feature_discover.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/oauth.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/password_reset.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/reachability.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/phases/sms_mfa.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/report.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/cli/validate/runner.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/config/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/config/loader.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/config/secrets.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/base.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/composer.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/console.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/factory.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/ses.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/smtp.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/hooks/events.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/_objectid.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/oauth_identity.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/oauth_state.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/models/user.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/oauth/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/oauth/base.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/oauth/errors.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/oauth/providers/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/oauth/registry.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/_helpers.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/account.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/admin.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/login.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/logout.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/oauth.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/password.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/phone.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/register.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/routers/verify.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/sms/base.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/sms/factory.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/sms/sns.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/pages.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/static/js/regstack.js +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/_token_handoff.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/oauth_complete.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/cli.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/routes.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/server.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/static/wizard.css +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/static/wizard.js +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/templates/wizard.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/validators.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/window.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/oauth_google/writer.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/_aws.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/cli.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/routes.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/server.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/static/wizard.css +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/static/wizard.js +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/templates/wizard.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/validators.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/window.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/ses/writer.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/__init__.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/cli.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/routes.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/server.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/static/designer.css +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/static/designer.js +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/templates/designer.html +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/validators.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/window.py +0 -0
- {regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/writer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regstack
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Embeddable user registration, login, and account management for FastAPI apps. SQLite / Postgres / MongoDB.
|
|
5
5
|
Project-URL: Homepage, https://github.com/jdrumgoole/regstack
|
|
6
6
|
Project-URL: Repository, https://github.com/jdrumgoole/regstack
|
|
@@ -15,6 +15,7 @@ class CheckResult:
|
|
|
15
15
|
ok: bool
|
|
16
16
|
detail: str
|
|
17
17
|
skipped: bool = False
|
|
18
|
+
warn: bool = False
|
|
18
19
|
|
|
19
20
|
@classmethod
|
|
20
21
|
def passed(cls, name: str, detail: str) -> CheckResult:
|
|
@@ -27,3 +28,11 @@ class CheckResult:
|
|
|
27
28
|
@classmethod
|
|
28
29
|
def skip(cls, name: str, detail: str) -> CheckResult:
|
|
29
30
|
return cls(name=name, ok=True, detail=detail, skipped=True)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def warned(cls, name: str, detail: str) -> CheckResult:
|
|
34
|
+
"""An advisory finding: not a hard failure (``ok=True`` so it
|
|
35
|
+
doesn't fail the command), but surfaced distinctly so operators
|
|
36
|
+
notice it. Used for things outside regstack's control that the
|
|
37
|
+
operator should act on — e.g. an out-of-date database server."""
|
|
38
|
+
return cls(name=name, ok=True, detail=detail, warn=True)
|
|
@@ -49,14 +49,23 @@ def doctor(config_path_in: Path | None, check_dns: bool, test_recipient: str | N
|
|
|
49
49
|
_run(toml_path=toml_path, check_dns=check_dns, test_recipient=test_recipient)
|
|
50
50
|
)
|
|
51
51
|
failed = sum(1 for r in results if not r.ok)
|
|
52
|
+
warned = sum(1 for r in results if r.warn)
|
|
52
53
|
for r in results:
|
|
53
|
-
|
|
54
|
+
if r.warn:
|
|
55
|
+
symbol = click.style("⚠", fg="yellow")
|
|
56
|
+
elif r.ok:
|
|
57
|
+
symbol = click.style("✔", fg="green")
|
|
58
|
+
else:
|
|
59
|
+
symbol = click.style("✘", fg="red")
|
|
54
60
|
click.echo(f"{symbol} {r.name}: {r.detail}")
|
|
61
|
+
if warned:
|
|
62
|
+
click.echo(click.style(f"\n{warned} advisory warning(s).", fg="yellow"), err=True)
|
|
55
63
|
if failed:
|
|
56
|
-
click.echo(click.style(f"
|
|
64
|
+
click.echo(click.style(f"{failed} check(s) failed.", fg="red"), err=True)
|
|
57
65
|
# Clamp to 0/1 so a shell `regstack doctor && deploy` is predictable;
|
|
58
|
-
#
|
|
59
|
-
#
|
|
66
|
+
# advisory warnings (e.g. an out-of-date DB server) do NOT fail the
|
|
67
|
+
# command — they're surfaced but exit 0. The failure count appears on
|
|
68
|
+
# the stderr line above for operators who want it. (Review #4.)
|
|
60
69
|
sys.exit(1 if failed else 0)
|
|
61
70
|
|
|
62
71
|
|
|
@@ -79,6 +88,9 @@ async def _run(
|
|
|
79
88
|
|
|
80
89
|
out.append(await _check_backend(config))
|
|
81
90
|
out.append(await _check_schema(config))
|
|
91
|
+
mongo_version = await _check_mongo_server_version(config)
|
|
92
|
+
if mongo_version is not None:
|
|
93
|
+
out.append(mongo_version)
|
|
82
94
|
out.append(_check_email_factory(config))
|
|
83
95
|
|
|
84
96
|
if check_dns:
|
|
@@ -156,6 +168,109 @@ async def _check_schema(config: RegStackConfig) -> CheckResult:
|
|
|
156
168
|
await backend.aclose()
|
|
157
169
|
|
|
158
170
|
|
|
171
|
+
# CVE-2025-14847 ("MongoBleed", CVSS 8.7): an unauthenticated network
|
|
172
|
+
# attacker can leak server memory via crafted zlib-compressed messages.
|
|
173
|
+
# The pymongo driver is not the vulnerable component — the server binary
|
|
174
|
+
# is. Patched server releases by LTS track. Keyed (major, minor) →
|
|
175
|
+
# minimum safe (major, minor, patch). See docs/security-reports/2026-05-20.md.
|
|
176
|
+
_MONGO_PATCHED_BASELINE: dict[tuple[int, int], tuple[int, int, int]] = {
|
|
177
|
+
(8, 2): (8, 2, 3),
|
|
178
|
+
(8, 0): (8, 0, 17),
|
|
179
|
+
(7, 0): (7, 0, 28),
|
|
180
|
+
(6, 0): (6, 0, 27),
|
|
181
|
+
(5, 0): (5, 0, 32),
|
|
182
|
+
(4, 4): (4, 4, 30),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# The newest LTS track we have a baseline for. A server on a track newer
|
|
186
|
+
# than this (e.g. 8.3+, 9.x) post-dates the advisory and is treated as safe.
|
|
187
|
+
_MONGO_NEWEST_KNOWN_TRACK = max(_MONGO_PATCHED_BASELINE)
|
|
188
|
+
# The oldest LTS track the advisory lists. Anything below it (3.6, 4.0,
|
|
189
|
+
# 4.2, …) is EOL and below every patched release — warn.
|
|
190
|
+
_MONGO_OLDEST_KNOWN_TRACK = min(_MONGO_PATCHED_BASELINE)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _parse_mongo_version(version: str) -> tuple[int, int, int] | None:
|
|
194
|
+
"""Parse a MongoDB version string like ``"7.0.5"`` into ``(7, 0, 5)``.
|
|
195
|
+
|
|
196
|
+
Tolerates a release-candidate / pre-release suffix (``"8.0.0-rc1"``)
|
|
197
|
+
by splitting on the first ``-``. Returns ``None`` if the leading
|
|
198
|
+
three dotted components aren't all integers.
|
|
199
|
+
"""
|
|
200
|
+
core = version.split("-", 1)[0]
|
|
201
|
+
parts = core.split(".")
|
|
202
|
+
if len(parts) < 3:
|
|
203
|
+
return None
|
|
204
|
+
try:
|
|
205
|
+
return (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
206
|
+
except ValueError:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _assess_mongo_server_version(version: str) -> tuple[bool, str]:
|
|
211
|
+
"""Decide whether ``version`` is safe against CVE-2025-14847.
|
|
212
|
+
|
|
213
|
+
Returns ``(ok, detail)``. ``ok`` is False only when the server is on
|
|
214
|
+
a known-affected LTS track *below* its patched baseline — that's the
|
|
215
|
+
case worth a WARNING. Newer-than-advisory and patched servers return
|
|
216
|
+
True; an unrecognised/older track returns True with a "verify
|
|
217
|
+
manually" note rather than crying wolf on every odd build string.
|
|
218
|
+
"""
|
|
219
|
+
parsed = _parse_mongo_version(version)
|
|
220
|
+
if parsed is None:
|
|
221
|
+
return True, f"server {version} (could not parse version; verify against CVE-2025-14847)"
|
|
222
|
+
track = (parsed[0], parsed[1])
|
|
223
|
+
baseline = _MONGO_PATCHED_BASELINE.get(track)
|
|
224
|
+
if baseline is not None:
|
|
225
|
+
if parsed < baseline:
|
|
226
|
+
patched = ".".join(str(n) for n in baseline)
|
|
227
|
+
return False, (
|
|
228
|
+
f"server {version} is vulnerable to CVE-2025-14847 (MongoBleed); "
|
|
229
|
+
f"upgrade to ≥ {patched}, or disable zlib compression in mongod.conf"
|
|
230
|
+
)
|
|
231
|
+
return True, f"server {version} (≥ {'.'.join(str(n) for n in baseline)}, patched)"
|
|
232
|
+
if track > _MONGO_NEWEST_KNOWN_TRACK:
|
|
233
|
+
return True, f"server {version} (newer than CVE-2025-14847 advisory tracks)"
|
|
234
|
+
if track < _MONGO_OLDEST_KNOWN_TRACK:
|
|
235
|
+
return False, (
|
|
236
|
+
f"server {version} is end-of-life and below every CVE-2025-14847 "
|
|
237
|
+
"patched release; upgrade to a supported, patched MongoDB version"
|
|
238
|
+
)
|
|
239
|
+
return True, (
|
|
240
|
+
f"server {version} on an unrecognised release track; verify against CVE-2025-14847 manually"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def _check_mongo_server_version(config: RegStackConfig) -> CheckResult | None:
|
|
245
|
+
"""Advisory check: warn if the MongoDB *server* is below the
|
|
246
|
+
CVE-2025-14847 patched baseline. Returns None for non-Mongo backends
|
|
247
|
+
(the check doesn't apply).
|
|
248
|
+
"""
|
|
249
|
+
from regstack.backends.base import BackendKind
|
|
250
|
+
|
|
251
|
+
backend = build_backend(config)
|
|
252
|
+
try:
|
|
253
|
+
if backend.kind is not BackendKind.MONGO:
|
|
254
|
+
return None
|
|
255
|
+
from regstack.backends.mongo import MongoBackend
|
|
256
|
+
|
|
257
|
+
assert isinstance(backend, MongoBackend)
|
|
258
|
+
info = await backend.database.command("buildInfo")
|
|
259
|
+
version = str(info.get("version", ""))
|
|
260
|
+
if not version:
|
|
261
|
+
return CheckResult.warned(
|
|
262
|
+
"mongo server", "buildInfo returned no version; verify against CVE-2025-14847"
|
|
263
|
+
)
|
|
264
|
+
ok, detail = _assess_mongo_server_version(version)
|
|
265
|
+
if ok:
|
|
266
|
+
return CheckResult.passed("mongo server", detail)
|
|
267
|
+
return CheckResult.warned("mongo server", detail)
|
|
268
|
+
except Exception as exc:
|
|
269
|
+
return CheckResult.warned("mongo server", f"version check failed: {exc}")
|
|
270
|
+
finally:
|
|
271
|
+
await backend.aclose()
|
|
272
|
+
|
|
273
|
+
|
|
159
274
|
def _check_email_factory(config: RegStackConfig) -> CheckResult:
|
|
160
275
|
try:
|
|
161
276
|
service = build_email_service(config.email)
|
|
@@ -98,11 +98,15 @@ class SmsConfig(BaseModel):
|
|
|
98
98
|
twilio_account_sid: str | None = None
|
|
99
99
|
twilio_auth_token: SecretStr | None = None
|
|
100
100
|
|
|
101
|
-
log_bodies: bool =
|
|
102
|
-
"""When True
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
log_bodies: bool = False
|
|
102
|
+
"""When True, the ``null`` backend logs the SMS body (including the
|
|
103
|
+
6-digit MFA code) at INFO. Defaults to False so a misconfigured
|
|
104
|
+
deployment can't leak codes into shared logs — symmetric with
|
|
105
|
+
``email.log_bodies``. Set it True for local dev when you want to
|
|
106
|
+
read the code out of stdout (the bundled examples instead surface
|
|
107
|
+
it via an ``mfa_login_started`` hook, so they don't need this).
|
|
108
|
+
Other backends ignore this flag — they never log message bodies.
|
|
109
|
+
(Security review 2026-05-19.)"""
|
|
106
110
|
|
|
107
111
|
|
|
108
112
|
class OAuthConfig(BaseModel):
|
|
@@ -16,6 +16,7 @@ installed and turns ``enable_oauth`` on.
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import asyncio
|
|
19
20
|
from typing import TYPE_CHECKING, Any
|
|
20
21
|
from urllib.parse import urlencode
|
|
21
22
|
|
|
@@ -162,7 +163,14 @@ class GoogleProvider(OAuthProvider):
|
|
|
162
163
|
expected_nonce: str,
|
|
163
164
|
) -> OAuthUserInfo:
|
|
164
165
|
try:
|
|
165
|
-
|
|
166
|
+
# PyJWKClient's fetch is synchronous urllib; on a cache miss it
|
|
167
|
+
# would block the event loop for the round-trip to Google's JWKS
|
|
168
|
+
# endpoint. Push it to a worker thread so concurrent requests
|
|
169
|
+
# aren't stalled. (Security review 2026-05-20 · I-2.)
|
|
170
|
+
signing_key_obj = await asyncio.to_thread(
|
|
171
|
+
self._jwks_client.get_signing_key_from_jwt, id_token
|
|
172
|
+
)
|
|
173
|
+
signing_key = signing_key_obj.key
|
|
166
174
|
except Exception as exc: # PyJWKClient raises a grab-bag — collapse to ours
|
|
167
175
|
raise OAuthIdTokenError(f"jwks lookup failed: {exc}") from exc
|
|
168
176
|
try:
|
|
@@ -10,14 +10,15 @@ log = logging.getLogger("regstack.sms.null")
|
|
|
10
10
|
class NullSmsService(SmsService):
|
|
11
11
|
"""Default backend. Records messages in ``self.outbox`` so tests and dev
|
|
12
12
|
runs can inspect them without contacting a real SMS gateway. Logs each
|
|
13
|
-
send at INFO
|
|
13
|
+
send at INFO.
|
|
14
14
|
|
|
15
|
-
``log_bodies`` (default
|
|
16
|
-
(containing the 6-digit code) is included in the log line.
|
|
17
|
-
|
|
15
|
+
``log_bodies`` (default False) controls whether the message body
|
|
16
|
+
(containing the 6-digit code) is included in the log line. It defaults
|
|
17
|
+
off so a misconfigured deployment can't leak codes into shared logs;
|
|
18
|
+
flip it on for local dev when you want to read the code out of stdout.
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
|
-
def __init__(self, *, log_bodies: bool =
|
|
21
|
+
def __init__(self, *, log_bodies: bool = False) -> None:
|
|
21
22
|
self.outbox: list[SmsMessage] = []
|
|
22
23
|
self._log_bodies = log_bodies
|
|
23
24
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.2"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/blacklist_repo.py
RENAMED
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/login_attempt_repo.py
RENAMED
|
File without changes
|
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/oauth_identity_repo.py
RENAMED
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/mongo/repositories/oauth_state_repo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/migrations/versions/0002_oauth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/login_attempt_repo.py
RENAMED
|
File without changes
|
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/oauth_identity_repo.py
RENAMED
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/backends/sql/repositories/oauth_state_repo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{regstack-0.8.1 → regstack-0.8.2}/src/regstack/wizard/theme_designer/templates/designer.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|