Skip to content

Add Apple and Google sign-in with optional two-factor authentication - #277

Merged
jmfk merged 6 commits into
mainfrom
codex/social-login-mfa
Oct 24, 2026
Merged

jmfk merged 6 commits into
mainfrom
codex/social-login-mfa

Conversation

@jmfk

@jmfk jmfk commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Existing CMS accounts can now explicitly connect Apple or Google from Profile and optionally require a second factor after password or provider sign-in. React uses the same allauth browser-session flow for initial sign-in and expired sessions; an email match alone never links accounts or grants workspace access.

Changes

  • Pin django-allauth 65.20.1 and add TOTP, WebAuthn as a second factor, and once-visible single-use recovery codes. Require verified email and recent authentication for enrollment and account connections.
  • Encrypt activated MFA material and pending TOTP enrollment keys with a dedicated environment-specific keyring. Retain only allowlisted provider identity fields.
  • Add a user-bound security version so account-security changes revoke older sessions, personal JWT/DRF credentials and Kanban browser tickets. Recheck live WebSocket sessions; keep machine identities outside personal security management.
  • Replace browser localStorage JWT authentication with HttpOnly sessions and CSRF, preserving existing user IDs, passwords, roles, memberships, limited-role login and internal return destinations.
  • Add Profile security controls, password recovery compatibility, reverse-proxy routes, an explicit development auto-login switch, PRD/ADR and operator configuration documentation.

This branch excludes the separate diagnostics work in #275.

Local verification

  • cd backend && python manage.py test cms_auth --settings=cms_auth.test_settings --noinput — 36 passed. Includes real software WebAuthn registration/assertion, mocked Apple/Google callback boundaries, recovery-code reuse/replacement, encrypted storage, credential revocation, tenant membership preservation, actual ASGI sessions and the real Kanban consumer.
  • python manage.py makemigrations cms_auth --check --dry-run --settings=cms_auth.test_settings — no changes.
  • cd frontend && npx vitest run src/components/auth/__tests__/AuthenticationForm.test.jsx src/components/auth/__tests__/AccountSecurityPanel.test.jsx src/api/__tests__/auth.test.js src/api/__tests__/client.test.js src/contexts/__tests__/AuthContext.test.jsx src/pages/__tests__/LoginPage.inbox.test.jsx src/pages/__tests__/ProfilePage.test.jsx — 23 passed, including the two limited-role regressions already on main.
  • npm run build — passed; existing chunk-size warnings remain.
  • Changed Python files: Black 25.11.0, isort and flake8 — passed. Focused frontend ESLint — no errors; existing AuthContext Fast Refresh export warning remains.
  • Specs governance validator and git diff --check — passed.
  • Previous browser QA with synthetic responses covered login and Profile at 1280px and 390px without page errors or horizontal overflow.

The normal project test runner passed 35 tests (core.tests.AdminPasswordResetLinkTest cms_auth) using full project migrations on isolated PostgreSQL 17 with development auto-login disabled. The current standalone auth suite passes 36 tests. Broader unrelated suites were not repeated. Live provider acceptance needs ECEEE-owned credentials and a registered HTTPS test origin; PostgreSQL concurrency and the target's allauth upgrade history remain unverified.

Review fixes

  • Prevent in-flight security mutations from promoting already-revoked browser sessions to the new security version. Serialize the final login version check and session stamp with credential revocation under the same user-row lock. Regression tests cover stale/current sessions and obsolete pending login state.

  • Show rejected provider connections directly in Profile rather than initializing the message inside an unmounted reauthentication form. Tests cover both callback reasons and an ordinary Profile visit.

  • Correct operator-generated reset URLs to use allauth's user-ID format and return the serialized URL. Real API regressions cover redemption, one-time use, MFA preservation and credential revocation.

  • Make auth tests preserve the migration-created user set rather than assuming an empty database.

  • Keep expired-session authentication on the current page with password and MFA. Provider redirects remain on the login page; the dialog omits them to preserve unsaved editor work and queued requests.

  • Update browser login regressions and their mock to the allauth session/CSRF contract; verify that personal JWTs are not stored. Both targeted Playwright tests and fixture/spec lint passed locally.

CI status

Publisher's undici is updated from 7.30.0 to the patched 7.30.1 within the existing dependency range. Local verification passed: clean npm ci, npm audit --omit=dev --audit-level=high (zero vulnerabilities), 97 Publisher tests (two PostgreSQL-only tests skipped locally), lint, typecheck and production build/client-bundle verification. Previous head 8f58f15654632c37b0f546402a7450dcf43db7dc passed Fast CI and Full CI (40089125397). Review-fix head 59091e26 passes 36 isolated auth backend tests, 23 focused frontend tests, frontend build, changed-file Python/JS lint and git diff --check; Fast CI and Full CI, including both gate checks, succeeded for exact head 59091e26199635050d00293b52f1bc4af85930ea. The initial Fast CI import-options test failure did not reproduce in its isolated local test, a complete local frontend run (1,927 passing tests) or either remote rerun; no code was changed to obtain the rerun. PostgreSQL concurrency, live provider acceptance and browser visual checks were not repeated for these fixes. No merge or deployment has been performed.

Risk and rollout

This is an authentication/authorization change and requires security review. The application migration only creates the account revocation-state table; it does not backfill accounts, change memberships or manufacture email verification. Normal allauth migrations must also be reviewed against the target installation.

Provider configuration, MFA encryption-key provisioning and actual HTTPS acceptance are required before enabling the feature. No public signup, passwordless login, trusted-browser bypass or operator MFA recovery is added. After MFA enrollment, reverting to older code that ignores MFA would be unsafe; preserve data/keys and prefer a reviewed roll-forward.

This coherent authentication change is worth a full CI run. Full CI and fresh owner sign-off are required before merging; CI is not code-review approval. No merge or production deployment is authorized or performed.

Current-head merge risk review

Independent P0–P3 review is clean at 59091e26199635050d00293b52f1bc4af85930ea; no verified unresolved or ignored findings. GitHub has no submitted reviews, no review decision and no unresolved inline review threads; CI is separate evidence. Head is mergeable/CLEAN.

The CMS migration creates only a user-bound state table, with lazy rows and no user/email/membership backfill. Standard Django transactional migration tracking governs installation, upgrade and retry; there is no resumable data backfill. Per-user locks serialize revocation/login state changes. Full CI passed PostgreSQL integration/migration checks. The target's existing migration history, true PostgreSQL concurrency and real HTTPS provider acceptance remain unverified and must be addressed before rollout. Preserve MFA keys and security state, and prefer roll-forward; reverting to pre-MFA code after enrollment can bypass protection. No production mutation, data reset or restoration is included. Fresh explicit owner sign-off acknowledging these authentication/authorization and migration risks is still required to merge this exact head.

@jmfk jmfk added the full-ci Run every CI check before merge label Oct 24, 2026 — with ChatGPT Codex Connector
@jmfk jmfk removed the full-ci Run every CI check before merge label Oct 24, 2026
@jmfk jmfk added the full-ci Run every CI check before merge label Oct 24, 2026 — with ChatGPT Codex Connector
@jmfk jmfk removed the full-ci Run every CI check before merge label Oct 24, 2026
@jmfk jmfk added the full-ci Run every CI check before merge label Oct 24, 2026 — with ChatGPT Codex Connector
@jmfk
jmfk merged commit 86bf2bd into main Oct 24, 2026
28 of 30 checks passed
@jmfk
jmfk deleted the codex/social-login-mfa branch October 25, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Run every CI check before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant