Skip to content

[6.x] Modernize elevated sessions#19238

Open
brianjhanson wants to merge 10 commits into
6.xfrom
cms-19235-elevated-session-form
Open

[6.x] Modernize elevated sessions#19238
brianjhanson wants to merge 10 commits into
6.xfrom
cms-19235-elevated-session-form

Conversation

@brianjhanson

@brianjhanson brianjhanson commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Requires a user to re-confirm their password (an "elevated session") before a sensitive change goes through — toggling admin, granting new permissions/groups, changing a password, revealing a GraphQL token, and so on.

Replaces the legacy jQuery/Garnish login modal with a Vue-native confirmation flow, and enforces elevation server-side:

  • Server: RequireConfirmedPassword middleware (password.confirm alias) + ConfirmsPasswords::requireConfirmedPassword() return 423 when elevation is missing; a dedicated users/confirm-password endpoint drives the modal.
  • Client: a reactive elevatedSessionManager + <ElevatedSessionHost> modal (with 2FA / passkey fallbacks). Works both proactively (prompt before submit) and reactively (retry once on a 423).
  • Legacy compatibility: Craft.ElevatedSessionForm and Craft.elevatedSessionManager are shimmed onto the new code, so existing Twig screens keep working. The module follows the standard main-class / custom-element / index-shim shape (like listbox, generated-fields).

Usage

Inertia form — require elevation only when sensitive fields changed:

const {save} = useSettingsSave(form, routeAction, {
  passwordConfirmation: {
    required: ({admin, groups, permissions}) =>
      admin !== initialAdmin ||
      groups.some((g) => !initialGroups.has(g)) ||
      permissions.some((p) => !initialPermissions.has(p)),
  },
});

One-off action — run a callback behind an elevated session (auto-retries on 423):

await elevatedSessionManager.run(() =>
  connectRequest.post(connect({provider}).url)
);

Legacy Twig form — declarative custom element (or the Craft.ElevatedSessionForm global):

<craft-elevated-session-form inputs='["[name=\"admin\"]", "#newPassword"]'>
  <form id="main-form"></form>
</craft-elevated-session-form>

Server enforcement — middleware or inline:

Route::post('users/{userId}/permissions', [PermissionsController::class, 'update'])
    ->middleware('password.confirm');

// …or inline, only when it matters:
if ($adminParam) {
    $this->requireConfirmedPassword();
}

Fixes #19235

@brianjhanson brianjhanson marked this pull request as draft July 10, 2026 18:58
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/cpopen Storybook

No changed components detected in this Storybook.

resources/jsopen Storybook

No changed components detected in this Storybook.

brianjhanson and others added 4 commits July 10, 2026 14:37
Pulled in the colleague's elevated-session work and made it the base, since
it is comprehensively more complete than our branch:
- Vue-native modal (ElevatedSessionHost.vue) with real 2FA/passkey support via
  the server-captured cp.login.alternative-login-methods fragment, replacing our
  reuse of the legacy jQuery/Garnish login modal.
- Server enforcement: 423 status, RequireConfirmedPassword middleware
  (password.confirm alias), dedicated users/confirm-password endpoint.
- Reactive manager + both proactive (passwordConfirmation.required) and
  server-reactive (423 retry) triggers; LoginRateLimiter; comprehensive tests.

Reconciliation (favoring the better bit from each branch):
- Removed our superseded parallel module (resources/js/modules/elevated-session,
  common/composables/useElevatedSession) and its useSettingsSave elevatedFields
  API in favor of theirs.
- Kept our cleanup instinct: deleted the now-orphaned legacy JS — both
  ElevatedSessionForm.js (already removed on our side) and ElevatedSessionManager.js
  (left dangling/unimported on theirs).
- Added a root `vitest` runner (test script + happy-dom test block in
  vite.config.js) so their new resources/js/**/*.test.ts suites actually run;
  they had no configured project and would otherwise never execute.

Verified: vue-tsc 0 errors, eslint 0 errors, vite build ok, 26 frontend tests
pass, and elevated-session + login/provider Pest suites pass on a built DB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The skip-link token renames and permissions heading margin tweak that landed in
"First run through" are unrelated to the elevated-session work; they've been
split out to the design-token-updates branch (off 6.x).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@brianjhanson brianjhanson requested a review from riasvdv July 10, 2026 20:42
@brianjhanson brianjhanson marked this pull request as ready for review July 10, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants