Repository navigation
Gateway - #1
Merged
Merged
Gateway#1
Conversation
Modal app "gateway" serving a FastAPI instance at the "gateway" label with placeholder / and /healthz routes. Ignore .env so local secrets stay out of the repo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
itsdangerous URLSafeSerializer with separate salts for invite links and session cookies. Expiry is an explicit exp claim in the payload, checked in one _verify helper with an injectable now for testing. The signing key is read from the environment at call time so the module imports without Modal or a .env file. Tests cover round trips, exclusive expiry boundary, spliced-signature forgery, cross-salt rejection, key rotation, and missing key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
state.py owns the gateway-state Dict. new_invite builds a record with an injectable now for testing and returns the id alongside it; get, put, and list wrap the Dict under an invite: key prefix that never leaks past the module. Budget is tracked in GPU-seconds. Tests cover the record shape against a literal dict, id length, and id uniqueness. Dict accessors are untested by design. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
Local entrypoints that build and store an invite record, sign the link against the deployed gateway URL resolved via get_web_url, list invites, and flip the revoked flag. Reads SIGNING_KEY from a local .env. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
GET /i/{token} verifies the signed invite, checks the Dict record for
revocation and expiry, marks first redemption, and redirects to /menu
with a signed session cookie capped by the invite's expiry. /menu is a
placeholder that echoes the session payload. FastAPI app moved to
module level; the gateway Secret is attached at the App.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
require_session reads and verifies the cookie, then re-checks the Dict so revocation takes effect on the next request. /menu renders a Jinja2 template with the invite label and a placeholder demo list. Templates ship in the image via add_local_dir anchored to app.py's location. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
ruff lint and format via the ruff-pre-commit mirror, plus local mypy and pytest hooks that run from the gateway venv, all scoped to gateway/. Tool config lives in the root pyproject.toml. The unused gateway/__init__.py is removed so mypy, pytest, and Modal all see gateway/ as a folder of top-level modules. Remaining changes are the first ruff pass: import sorting, spacing, and Annotated[..., Depends(...)] for the menu route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
Give the gateway its own pyproject.toml and uv.lock so CI can rebuild the venv with --locked. Ruff and mypy config move there from the repo root, which also lets ruff recognize auth and state as first-party imports. The workflow runs ruff, mypy, and pytest on pull requests and pushes that touch gateway/, and runs modal deploy only on pushes to main. Also shorten the invite CLI defaults to 1 day and 60 seconds, and ignore plans/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
- Resolve the web URL before persisting a new invite so a missing deploy cannot leave an orphan record. - Stop writing redeemed_at in redeem; the unguarded read-modify-write could overwrite a concurrent revoke. - Build the container with uv_sync so it installs the locked versions CI tests instead of a hand-copied unpinned list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
Registering the local entrypoints on the gateway App made every `modal run invites.py` build the image, hydrate the Secret, and start an ephemeral web endpoint. The CLI only needs the named Dict and the signing key from .env. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
redeem and require_session now share one check for revoked and expired, so require_session also honors record expiry and the planned budget rule has a single home. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CJGmXer6J7V6XZ52t8Ujd
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.
Demo gateway: invite links and sessions
Adds
gateway/, a FastAPI app on Modal that gates demos behind signed invite links.modal run invites.py::create. The record lives in a Modal Dict; the link carries a signed{iid, exp}token.revoketakes effect on the next request.uv.lock.sequenceDiagram participant Op as Operator (CLI) participant D as Modal Dict participant G as Guest participant W as Gateway Op->>D: put invite:{iid} {expires, revoked, budget} Op-->>Op: sign_invite({iid, exp}) → /i/<token> G->>W: GET /i/<token> W->>W: verify_invite (signature + exp) W->>D: get invite:{iid} W->>W: is_invite_valid (revoked, expires) W-->>G: 303 /menu + Set-Cookie session (signed {iid, sid, exp}) G->>W: GET /menu (cookie) W->>W: read_session (signature + exp) W->>D: get invite:{iid} W->>W: is_invite_valid W-->>G: 200 menu Op->>D: revoke: revoked = true G->>W: GET /menu (cookie) W->>D: get invite:{iid} W-->>G: 303 /expired