Repository navigation
Conversation
This was referenced Oct 27, 2026
This was referenced Oct 27, 2026
4 tasks done
| updated = conn.execute( | ||
| update(login_challenges) | ||
| .where(login_challenges.c.id == challenge_id) | ||
| .values(attempts=login_challenges.c.attempts + 1) |
Contributor
There was a problem hiding this comment.
admit_login_attempt performs the eligibility SELECT and the incrementing UPDATE separately, but the UPDATE does not repeat attempts < MAX_ATTEMPTS (or otherwise lock the row). Under concurrent confirmations when attempts == MAX_ATTEMPTS - 1, both transactions can pass the SELECT, then both increment and return a challenge; one of them can therefore become the fourth admitted guess. Please make the increment conditional/atomic (for example, include attempts < policy.MAX_ATTEMPTS in the UPDATE and check its returned row) so the guessing cap holds under concurrency.
This branch has not been deployed
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.
What this changes
Adds opt-in “Sign in with Misty” for active directory members. The gateway owns challenge state and admission limits; private verification generates, delivers, and verifies codes; the compute platform owns its sessions.
The external endpoints are
POST /v1/auth/email/start,POST /v1/auth/email/confirm,GET /v1/auth/issuer, andGET /v1/auth/people/{person_id}/status, protected by separateauth:emailandauth:statusscopes. Start responses are generic, challenges are consumer-bound, eligibility is rechecked at confirmation, and redemption is atomic and single-use. Successful responses attest the gateway environment.Three guesses per challenge and shared mailbox delivery limits bound guessing while preserving other delivered codes. A known pre-send verification cooldown permits one deferred retry using the same challenge and reservation; ambiguous failures never retry. Status and edge-IP budgets are configured together with startup validation.
The security follow-up adds:
no-storeon login responses. HTTP/1 login 408/413/429 responses close their connections so incomplete uploads cannot linger.Why
Provides the identity boundary for the companion compute integration: UTMIST/Mist#125. Compute receives neither directory database access nor private verification credentials.
Zone
This deliberately spans gateway, verification, shared-auth documentation, platform documentation, and CI: gateway needs a separately scoped private verification identity, and their documented service boundary must change together. The security follow-up changes gateway code only.
Changed-file rationale
Paths below are repository-relative. Every changed file is covered; grouped files share the stated purpose.
services/gateway/contracts/login.pyservices/gateway/contracts/directory.py,services/gateway/contracts/storage.pyservices/gateway/src/login_policy.pyservices/gateway/src/storage/schema.py,services/gateway/migrations/versions/002_email_login.pyservices/gateway/src/storage/in_memory.py,services/gateway/src/storage/postgres.pyservices/gateway/src/api/routers/email_login.pyservices/gateway/src/api/app.py,services/gateway/src/api/middleware.pyservices/gateway/src/api/deps.pyservices/gateway/src/directory/http_client.pyservices/gateway/src/verification/__init__.py,services/gateway/src/verification/http_client.pyservices/gateway/src/config.py,services/gateway/src/cli.pyservices/gateway/tests/test_email_login.py,services/gateway/tests/test_verification.pyservices/gateway/tests/test_login_boundary.pyservices/gateway/tests/login_storage_cases.py,services/gateway/tests/test_login_storage.py,services/gateway/tests/test_postgres_adapter.pyservices/gateway/tests/test_config.py,services/gateway/tests/test_health.pyservices/gateway/tests/smoke_stack.py,.github/workflows/ci.ymlservices/verification/src/config.py,services/verification/src/api/deps.py,services/verification/src/api/app.pyservices/verification/src/storage/null_keys.py(removed)services/verification/src/api/routers/verification.py,services/verification/tests/test_request_code.pyservices/verification/tests/conftest.py,services/verification/tests/test_consumer_auth.pyservices/gateway/.env.example,services/gateway/README.md,services/gateway/docs/API.md,services/gateway/docs/ARCHITECTURE.md,services/gateway/docs/CONTRIBUTING.md,services/gateway/docs/DEPLOYMENT.mdservices/verification/.env.example,services/verification/README.md,services/verification/docs/API.md,services/verification/docs/ARCHITECTURE.md,services/verification/docs/DEPLOYMENT.mdAGENTS.md,README.md,docs/ARCHITECTURE.md,docs/DEVELOPMENT.md,docs/RAILWAY-DEPLOYMENT.md,packages/auth/README.mdThe staging merge preserves the current wiki-oriented README and retirement of historical audit files. File rationale and validation are recorded here instead of restoring
services/gateway/docs/CHANGE-AUDIT.md.How to verify
Security follow-up checks completed locally:
git diff --check.Expect: 100-continueprobes, malformed Unicode inputs, and concurrent admission in both adapters. With two hourly admissions remaining, 32 simultaneous requests admitted exactly two; rejected requests consumed no app quota.The staging merge changes no gateway, verification, or shared-auth implementation/tests, and leaves the Python lockfile unchanged.
Earlier feature validation recorded on this PR includes both service suites with disposable PostgreSQL, migration preservation of existing keys, three-service Docker HTTP smoke, synthetic browser-to-compute login/retirement flows, cooldown collision regressions, and repository-wide
make check. Those broader integration checks were not repeated during the security-only follow-up.Reproduce the HTTP integration with current images:
Checklist
staging; staging conflicts resolved.Deployment notes
EMAIL_LOGIN_ENABLED=false). Gateway migration 002 adds challenge/quota tables without changing existing key rows.Retry-Aftersupport before enabling gateway login. Provision a distinctvf_consumer withverification:write; preserve the bot's bootstrap credential.people:readandidentifiers:read, plus private verification URL/key configuration. Compute receives only a scopedgw_key withauth:emailandauth:status.GATEWAY_ENVexplicitly. Compute must pin the expected origin/issuer and use reviewed mappings for existing accounts. Keep staging and production credentials/databases separate.See the deployment runbook for provisioning and rollback.