Skip to content

🔐 feat: Enforce Required Two-Factor Authentication - #14731

Merged
berry-13 merged 10 commits into
devfrom
feat/14725-enforce-2fa
Oct 1, 2026
Merged

berry-13 merged 10 commits into
devfrom
feat/14725-enforce-2fa

Conversation

@berry-13

@berry-13 berry-13 commented Aug 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds deployment-wide two-factor authentication enforcement through ENFORCE_TWO_FACTOR_AUTHENTICATION=true.

When enabled, local password and LDAP users without 2FA receive a short-lived, purpose-scoped setup token instead of a full authenticated session. Enrollment requires TOTP verification, backup-code download, one-time acknowledgement, and one-time finalization before session tokens are issued. Refresh sessions and protected API routes also fail closed while required enrollment is incomplete.

The startup configuration advertises the policy to the client. The required setup flow preserves deep links across reload and finalization. Account settings keep the standard Disable 2FA button visible, disable it with a not-allowed cursor, and show Required by administrator on hover or keyboard focus. Federated identity providers remain responsible for their own MFA policies.

The enrollment state transitions are atomic, one-time credentials cannot be replayed, sensitive enrollment fields are excluded from user responses and caches, and user-document authentication caches are invalidated after promotion.

Closes #14725

Screenshots

Required enrollment (dark): local password and LDAP users without 2FA land here instead of a full session.

Required two-factor setup in dark mode

Account settings (dark): Disable 2FA stays visible, uses a not-allowed cursor, and shows Required by administrator on hover or keyboard focus.

Required two-factor settings in dark mode

Change Type

  • New feature (non-breaking change which adds functionality)

Testing

  • git diff --name-only -z 9bb599435f399a573b5284c91f7c2caadeef0db9..HEAD -- '*.js' '*.jsx' '*.ts' '*.tsx' | xargs -0 npx eslint
  • git diff --check 9bb599435f399a573b5284c91f7c2caadeef0db9..HEAD
  • (cd packages/data-provider && npm run build)
  • (cd packages/data-schemas && npm run build)
  • (cd packages/api && npm run build)
  • (cd client && npm run build)
  • (cd client && npm run typecheck)
  • (cd packages/api && npx tsc --noEmit)
  • (cd packages/api && npx jest src/auth/twoFactor.spec.ts --runInBand --silent) (54 tests)
  • (cd packages/data-schemas && npx jest src/methods/user.methods.spec.ts --runInBand --coverage=false --silent) (76 tests)
  • (cd api && npx jest server/controllers/AuthController.spec.js server/controllers/__tests__/TwoFactorController.spec.js server/controllers/auth/LoginController.spec.js server/controllers/auth/TwoFactorAuthController.spec.js server/middleware/__tests__/requireJwtAuth.spec.js server/middleware/limiters/twoFactorTempLimiter.test.js server/routes/admin/auth.refresh.test.js server/routes/auth.2fa-ratelimit.test.js server/routes/auth.cloudfront.test.js server/routes/auth.reset-password-ratelimit.test.js server/routes/__tests__/config.spec.js --runInBand --detectOpenHandles --silent) (208 tests)
  • (cd client && JEST_JUNIT_OUTPUT_DIR=/tmp JEST_JUNIT_OUTPUT_NAME=final-2fa-client.xml npx jest src/components/Nav/Settings/__tests__ src/components/Nav/SettingsTabs/Account src/components/Auth/__tests__/TwoFactorSetupScreen.spec.tsx src/hooks/__tests__/AuthContext.spec.tsx src/routes/__tests__/StartupLayout.spec.tsx src/utils/__tests__/redirect.test.ts --runInBand --coverage=false --silent) (128 tests)
  • CI=1 npx playwright test --config=e2e/playwright.config.mock.ts e2e/specs/mock/two-factor-required.spec.ts --repeat-each=3 (3 passes)

Test Configuration:

  • Node.js 26.5.1
  • Chromium through the mock Playwright configuration
  • MongoMemoryServer for data-schema integration coverage
  • Mock E2E startup explicitly disables inherited deployment enforcement; required-policy responses are mocked by the focused scenario

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Copilot AI lite review requested due to automatic review settings August 10, 2026 19:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@danny-avila danny-avila changed the title 🔐 feat: Enforce Two-Factor Authentication 🔐 feat: Enforced Required Two-Factor Authentication Aug 10, 2026
@danny-avila danny-avila changed the title 🔐 feat: Enforced Required Two-Factor Authentication 🔐 feat: Enforced Required 2FA Aug 10, 2026
@berry-13 berry-13 changed the title 🔐 feat: Enforced Required 2FA 🔐 feat: Enforce Required Two-Factor Authentication Aug 11, 2026
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70d092a448

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/data-schemas/src/methods/user.ts Outdated
Comment thread api/server/middleware/requireJwtAuth.js Outdated
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f5271ef69

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/middleware/requireJwtAuth.js Outdated
Comment thread api/server/middleware/requireJwtAuth.js Outdated
Comment thread api/server/routes/auth.js
Comment thread packages/data-schemas/src/methods/user.ts
Comment thread client/src/components/Auth/TwoFactorSetupScreen.tsx Outdated
@berry-13
berry-13 marked this pull request as ready for review August 15, 2026 04:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f06e0deb6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/auth/twoFactor.ts Outdated
Comment thread api/server/controllers/auth/TwoFactorAuthController.js Outdated
@berry-13
berry-13 force-pushed the feat/14725-enforce-2fa branch from 8f06e0d to 8254002 Compare August 15, 2026 04:17
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82540021d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/middleware/requireJwtAuth.js Outdated
@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review

@berry-13
berry-13 force-pushed the feat/14725-enforce-2fa branch from 71f3478 to 421c7f1 Compare August 15, 2026 16:52
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 421c7f1919

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/data-provider/src/request.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f90b71243

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/controllers/auth/LoginController.js Outdated
Comment thread api/server/middleware/blockRetiredSetupToken.js Outdated
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e098a84c0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/middleware/validateImageRequest.js Outdated
@berry-13 berry-13 linked an issue Aug 20, 2026 that may be closed by this pull request
1 task done
@danny-avila danny-avila added 🗺️ Two Factor Auth codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9) 🛡️ security review labels Sep 24, 2026
@berry-13
berry-13 changed the base branch from dev to canary September 28, 2026 21:00
@berry-13
berry-13 force-pushed the feat/14725-enforce-2fa branch from e098a84 to be87dfc Compare September 28, 2026 21:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-01T15:10:25.430812Z fb74790 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be87dfc4b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/auth/twoFactor.ts
Comment thread api/server/controllers/AuthController.js
@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

│ 23      │ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 │ 2996.3799999999464 │ 3756.4679999999353 │ 200    │
│ 24      │ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             │ 2998.777000000002  │ 4263.95199999999   │ 200    │
│ 25      │ 'http://localhost:3080/api/keys?name=openAI'                                                                    │ 3279.48199999996   │ 3920.1269999999786 │ 200    │
│ 26      │ 'http://localhost:3080/api/presets'                                                                             │ 3281.155999999959  │ 3929.591000000015  │ 200    │
│ 27      │ 'http://localhost:3080/api/tags'                                                                                │ 3281.7889999999898 │ 3930.05899999995   │ 200    │
│ 28      │ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     │ 3281.9919999999693 │ 4264.6329999999725 │ 200    │
│ 29      │ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       │ 3282.7819999999483 │ 4426.334999999963  │ 200    │
│ 30      │ 'http://localhost:3080/api/files/config'                                                                        │ 3282.9770000000135 │ 4184.401999999944  │ 200    │
│ 31      │ 'http://localhost:3080/api/user/settings/favorites/tools'                                                       │ 3283.198999999964  │ 4435.59799999994   │ 200    │
│ 32      │ 'http://localhost:3080/api/endpoints/token-config'                                                              │ 3284.6999999999534 │ 4440.164999999921  │ 200    │
│ 33      │ 'http://localhost:3080/api/user/settings/skills/active'                                                         │ 3285.0139999999665 │ 4771.986999999965  │ 200    │
│ 34      │ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        │ 3285.329999999958  │ 7287.426999999967  │ 200    │
│ 35      │ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              │ 3285.6329999999725 │ 4774.862999999954  │ 200    │
│ 36      │ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' │ 4532.936999999976  │ 4788.375999999931  │ 200    │
└─────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────┴────────────────────┴────────┘

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

┌─────────┬────────────────────────────┬──────────────────────┬───────┐
│ (index) │ audit                      │ median               │ limit │
├─────────┼────────────────────────────┼──────────────────────┼───────┤
│ 0       │ 'largest-contentful-paint' │ 4581.148             │ 4500  │
│ 1       │ 'cumulative-layout-shift'  │ 0.017901191865131477 │ 0.1   │
│ 2       │ 'total-blocking-time'      │ 301.86100000000033   │ 500   │
└─────────┴────────────────────────────┴──────────────────────┴───────┘

  1) [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4581.148

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


🤖: global teardown has been started
2026-09-29 06:14:54 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-29 06:14:54 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ✅  Found user in Database
🤖:  ✅  Deleted 1 convos & 2 messages
🤖:  ✅  Deleted user from Database
🤖: global teardown has been started
2026-09-29 06:14:55 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-29 06:14:55 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ⚠️  User not found in Database
  1 failed
    [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

Open the full run

@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

│ 23      │ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 │ 3067.9130000000296 │ 3830.886999999988  │ 200    │
│ 24      │ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             │ 3068.4679999999935 │ 4338.315999999992  │ 200    │
│ 25      │ 'http://localhost:3080/api/keys?name=openAI'                                                                    │ 3370.1419999999925 │ 3981.683000000019  │ 200    │
│ 26      │ 'http://localhost:3080/api/presets'                                                                             │ 3371.6749999999884 │ 3986.149999999994  │ 200    │
│ 27      │ 'http://localhost:3080/api/tags'                                                                                │ 3372.555000000022  │ 3989.589000000007  │ 200    │
│ 28      │ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     │ 3372.792000000016  │ 4339.209000000003  │ 200    │
│ 29      │ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       │ 3373.021000000008  │ 4493.945999999996  │ 200    │
│ 30      │ 'http://localhost:3080/api/files/config'                                                                        │ 3373.228999999992  │ 4242.676999999996  │ 200    │
│ 31      │ 'http://localhost:3080/api/user/settings/favorites/tools'                                                       │ 3375.0470000000205 │ 4495.770000000019  │ 200    │
│ 32      │ 'http://localhost:3080/api/endpoints/token-config'                                                              │ 3376.220000000001  │ 4499.938999999984  │ 200    │
│ 33      │ 'http://localhost:3080/api/user/settings/skills/active'                                                         │ 3376.774000000005  │ 4852.176999999996  │ 200    │
│ 34      │ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        │ 3377.2270000000135 │ 7368.708000000013  │ 200    │
│ 35      │ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              │ 3377.4459999999963 │ 4852.543999999994  │ 200    │
│ 36      │ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' │ 4619.524000000005  │ 4875.789000000019  │ 200    │
└─────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────┴────────────────────┴────────┘

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

┌─────────┬────────────────────────────┬─────────────────────┬───────┐
│ (index) │ audit                      │ median              │ limit │
├─────────┼────────────────────────────┼─────────────────────┼───────┤
│ 0       │ 'largest-contentful-paint' │ 4538.214            │ 4500  │
│ 1       │ 'cumulative-layout-shift'  │ 0.01790359181186702 │ 0.1   │
│ 2       │ 'total-blocking-time'      │ 356.53099999999995  │ 500   │
└─────────┴────────────────────────────┴─────────────────────┴───────┘

  1) [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4538.214

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


🤖: global teardown has been started
2026-09-29 06:20:05 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-29 06:20:05 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ✅  Found user in Database
🤖:  ✅  Deleted 1 convos & 2 messages
🤖:  ✅  Deleted user from Database
🤖: global teardown has been started
2026-09-29 06:20:06 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-29 06:20:06 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ⚠️  User not found in Database
  1 failed
    [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

Open the full run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3f7651a7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/controllers/TwoFactorController.js
Comment thread packages/api/src/auth/enrollment.ts Outdated
user: StoredTwoFactorAccount,
userId: string,
): Promise<void> => {
await deps.deleteAllUserSessions({ userId });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear minted cookies even when session deletion fails

When password recovery races challenge redemption or finalization and the post-mint retirement lookup throws, setAuthTokens has already queued refresh and CloudFront cookies before this cleanup runs. If deleteAllUserSessions also rejects, this helper exits before any cookies are cleared, and the outer catch sends a 500 carrying the newly minted post-cutoff credentials, which can refresh once the database recovers. Fresh evidence beyond the previously resolved cleanup thread is this fallible deletion-before-cookie-clear ordering; clear the client credentials in a finally path and ensure a failed deletion cannot leave the minted session usable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d9287b0: login, local refresh and enrollment now share one withdrawMintedSession that clears the refresh, provider and CDN cookies before awaiting the session deletion, so a deletion failure cannot leave the minted cookies on the 500. Covered in twoFactor.spec.ts (rejecting deletion still clears).

Comment thread client/src/components/Auth/TwoFactorSetupScreen.tsx
Comment thread packages/api/src/auth/gates.ts Outdated
userId,
process.env.JWT_SECRET as string,
);
const retirement = await deps.getUserById(userId, TOKEN_RETIREMENT_FIELDS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scope the gate lookup before tenant middleware

When TENANT_ISOLATION_STRICT=true, protected routes outside /api/auth reach this gate with only request-level context because tenantContextMiddleware is invoked later in the onAllowed callback. This getUserById therefore queries the tenant-isolated User model without either a tenant or system scope and throws, so an already-signed-in unenrolled user sees a 500 instead of the 403 setup handoff after enforcement is enabled. Establish the authenticated tenant context around the gate or run this retirement lookup in an explicitly appropriate scope.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4e92f92: the gate's retirement read now runs in runAsSystem, matching the JWT strategy's own user read, so strict tenant isolation reaches the 403 hand-off instead of throwing. packages/api/src/auth/gates.spec.ts asserts the read's scope and fails without the change.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9287b076a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/auth/twoFactor.ts
Require local and LDAP accounts to enroll in TOTP before they can use the
app when ENFORCE_TWO_FACTOR_AUTHENTICATION is set. Sign-in, refresh, bearer
verification, optional share auth and image authorization all route an
unenrolled account into a staged setup flow, and every bearer credential
minted before enrollment or a password reset is retired.

Password reset retirement uses the existing credentialsChangedAt stamp, and
commitPasswordReset clears any staged enrollment in the same write. Passkey
sign-in follows the same enrollment gate as password sign-in.
@berry-13
berry-13 force-pushed the feat/14725-enforce-2fa branch from bfd6883 to fb74790 Compare October 1, 2026 15:00
@berry-13
berry-13 requested a review from danny-avila as a code owner October 1, 2026 15:00
@berry-13
berry-13 changed the base branch from canary to dev October 1, 2026 15:00
@berry-13
berry-13 changed the base branch from dev to canary October 1, 2026 15:19
@berry-13
berry-13 changed the base branch from canary to dev October 1, 2026 15:19
@berry-13 berry-13 closed this Oct 1, 2026
@berry-13 berry-13 reopened this Oct 1, 2026
@berry-13
berry-13 merged commit 92432e9 into dev Oct 1, 2026
45 checks passed
@berry-13
berry-13 deleted the feat/14725-enforce-2fa branch October 1, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛡️ security review 🗺️ Two Factor Auth codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement]: Setting to enforce 2FA

3 participants