v0.2: setup wizard, EIP-4361 strict mode, EFP/ENS gates, audit log + webhooks#3
Merged
Conversation
`verify_siwe_message` now binds the optional EIP-4361 fields to the issued nonce: signed `Resources` must be a subset of the issued list, `Request ID` must match, and `Not Before` must match. A new `CLOCK_SKEW_SECONDS` setting (default 60) tolerates small client/server clock drift on `Issued At` / `Not Before` / `Expiration Time` checks via the `timestamp` parameter on the upstream `siwe` library. The nonce response surfaces the bound optional fields so clients can replay them into the signed message verbatim.
Adds integration tests for the EIP-6492 path through `verify_siwe_message`, asserting that wrapped signatures route through the upstream universal validator and that the request is rejected when no RPC is available for the chain. Documents the EIP-1271 / EIP-6492 contract wallet support in the README.
`siwe_django.recap` ships `encode_recap`, `decode_recap`, `find_recap_in_resources`, and `build_recap_statement` so apps can scope sign-ins to specific capabilities. The encoded URN goes into the SIWE message's `Resources` list as the final entry per spec; verification of that ReCap is already covered by the new resources-subset check from the EIP-4361 strict-mode commit.
Adds typed wrappers for the EthID/EFP API endpoints we need for the
upcoming EFP-gated sign-in flow:
- fetch_efp_stats(address) -> follower / following counts
- fetch_efp_follower_state(viewer, target) -> {follow, block, mute}
- fetch_efp_followers / fetch_efp_following with limit + offset
- fetch_efp_tags(address, source=...) with optional tagger filter
- fetch_ens_record(address)
All helpers degrade gracefully (return zeros / empty list) on lookup
failure so callers can compose them without nested error handling.
Extends the existing TOKEN_GATES dispatcher with seven new types backed by the EthID/EFP API: - efp_follower_of, efp_followed_by, efp_mutual: gate by social-graph edges between the wallet and a hub account. - efp_min_followers: gate by absolute follower count. - efp_tag: gate by tags applied to the wallet by a hub account. - efp_not_blocked_by: gate by absence of block / mute records. - ens_required: gate by presence of a primary ENS name. EFP / ENS gates ignore chain_id and reuse the existing group-sync semantics: a passing gate adds the configured Django group, a failing one removes it. README updated with the gate type table.
Ships a Typer-based CLI behind the new `cli` extra so the runtime package
keeps zero CLI dependencies. Subcommands:
- `siwe-django init` — patches `settings.py` (INSTALLED_APPS,
AUTHENTICATION_BACKENDS, SIWE_DJANGO block) and root `urls.py`
(mounts siwe_django.urls or siwe_django.drf.urls) via libcst, then
optionally drops a bundled Django sign-in template and runs
`manage.py migrate`. Idempotent: re-running detects the no-op and
exits cleanly.
- `siwe-django doctor` — diagnoses a configured project: pings RPC URLs,
checks the EthID API, flags ALLOWED_CHAIN_IDS without RPCs, missing
DOMAIN/URI. Supports `--json` for CI; non-zero exit on errors.
- `siwe-django scaffold-templates` — copies the bundled
`templates/siwe_django/siwe_login.html` (a no-build, vanilla-JS sign-in
page using `window.ethereum`) into the target project and registers a
URL include.
- `siwe-django migrate-from-payton` — best-effort regex rewrite of a
`payton/django-siwe-auth` project: renames the package, points imports
at the new model class names, and prints a follow-up checklist for
the parts that need a real data migration.
The bundled template also ships at
`src/siwe_django/templates/siwe_django/siwe_login.html` so adopters who
do not run the wizard can still `{% include %}` it.
A minimal Django project (no React, no build step) that renders the bundled `siwe_django/siwe_login.html` template against the standard SIWE endpoints. Mirrors the output of `siwe-django scaffold-templates` and serves as the canonical no-JS-toolchain reference alongside the existing React showcase.
Adds a `SiweAuthEvent` model that records nonce / verify / link / unlink / logout events with IP, user-agent, success flag, error code, and a free-form metadata JSON. Both the vanilla and DRF view layers call into a small `audit.record_event` helper after each action. Logging is opt-in via the new `AUDIT_ENABLED` setting (default True) so apps that route audit data elsewhere can disable the DB writes without losing functionality.
Introduces a `NonceStore` protocol with a `NonceRecord` dataclass and two implementations: - `DjangoOrmNonceStore` (default) — wraps the existing SiweNonce model. - `RedisNonceStore` (`siwe-django[redis]` extra) — uses `SET NX EX` for save and atomic delete for consume; replay protection is enforced because the second consumer's delete returns 0. The store is resolved via the new `NONCE_STORE` dotted-path setting, defaulting to the ORM backend so existing installs are unchanged. `services.issue_nonce` / `_load_nonce` / `_consume_nonce` route through the store instead of touching the model directly.
`SIWE_DJANGO["WEBHOOKS"]` accepts subscribers shaped
`{event, url, secret, timeout?}`. After every recorded auth event the
audit helper builds a canonical JSON payload, signs it with HMAC-SHA256
(`X-Siwe-Signature: sha256=<hex>`), and POSTs to each matching
subscriber. `event: "*"` is a wildcard.
Default delivery is synchronous best-effort urllib (failures are logged
but never block sign-in). Apps can plug in Celery / RQ by setting
`WEBHOOK_DISPATCHER` to a callable that receives
`(event, payload, subscriptions)`.
Adds a session-bound "last verified at" stamp written by every successful verify (vanilla + DRF). New `POST /reauth/` accepts a fresh SIWE message + signature for the authenticated user, refreshes the stamp, and rejects messages signed by wallets not linked to the user. Sensitive views can gate themselves with `@require_recent_siwe(seconds=300)` which returns `403 stepup_required` when the session has no fresh verify.
Adds an `openapi` extra (drf-spectacular>=0.27) and decorates every DRF view with `@extend_schema(tags=["siwe"])` plus a short summary so apps that wire drf-spectacular get a usable OpenAPI 3.1 contract for free. A passthrough shim in `siwe_django.drf.schema` keeps the views importable when drf-spectacular is not installed, so the `openapi` extra stays strictly opt-in.
Captures the EIP-4361 strict-mode work, EIP-6492 coverage, ReCap helpers, EFP / ENS social graph gates, the setup wizard CLI, the bundled Django sign-in template, the audit log, the pluggable nonce store with Redis backend, signed webhooks, step-up auth, and the opt-in drf-spectacular schemas.
`json.dumps` was outside the try block, so a `TypeError` from richer metadata (datetime, Decimal, model instance) propagated up through dispatch_webhook → record_event → the verify view and surfaced as a 500 even though `auth_login` had already committed the session. Now we serialize inside try/except and return False on failure, matching the module's documented best-effort semantics.
Hard-coded `python` fails on systems that only ship `python3` (notably stock macOS without a venv on PATH). subprocess.run then raises FileNotFoundError instead of returning a non-zero exit code, so the graceful failure branch is bypassed and the CLI crashes. Use sys.executable so the migrate step always runs under the same interpreter that's running the wizard.
Multi-stage Dockerfile builds the Vite bundle with bun, then serves it plus the Django endpoints from a single gunicorn process behind WhiteNoise so CSRF / sessions stay same-origin. Settings are now env-driven (ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, DOMAIN, URI, database path, secure-cookie / X-Forwarded-Proto flags) with the existing localhost defaults preserved for local dev. A new catch-all SPA view returns the built index.html for any unmatched path; Vite is taught to honour VITE_BASE so the production build emits asset URLs under /static/. `fly.toml` runs on shared-cpu-1x:512mb with a 1 GB volume mounted at /data for SQLite and an explicit migrate release_command. The README now documents the launch / volumes / secrets / deploy workflow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a Typer-based
siwe-djangosetup wizard (init/doctor/scaffold-templates/migrate-from-payton) under the newcliextra, a bundled Django sign-in template, and anexamples/templates_demo/non-React reference.Tightens spec compliance:
verify_siwe_messagenow bindsResources/Request ID/Not Beforeto the issued nonce, applies aCLOCK_SKEW_SECONDStolerance, and the EIP-6492 counterfactual signature path is covered with tests;siwe_django.recapships ERC-5573 helpers.Turns the EthID/EFP social graph into an authorization primitive via seven new gate types (
efp_follower_of,efp_followed_by,efp_mutual,efp_min_followers,efp_tag,efp_not_blocked_by,ens_required) plus typed helpers inethid.py.Adds production hardening:
SiweAuthEventaudit log, pluggableNonceStorewith a Redis backend (siwe-django[redis]), HMAC-SHA256 signed webhooks dispatched from audit events, step-up auth (POST /reauth/+@require_recent_siwe), and opt-in drf-spectacular schemas (siwe-django[openapi]).Bumps to v0.2.0; 151 tests pass.