volto icon indicating copy to clipboard operation
volto copied to clipboard

fix( protected pages or controlpanels): show error page instead of b…

Open ravi-aman opened this issue 1 month ago • 5 comments

Pull Request Documentation: Fix Protected Pages Blank Screen Bug

🐛 Issue

Closes #6736
Related to #6738

Title: Protected Pages Show Blank Screen Instead of Redirecting to Login

📝 Problem Description

When unauthenticated users accessed protected control panel pages (e.g., /controlpanel/site), they saw a blank white screen instead of being shown an error message with a login link.

Root Cause Analysis

The bug had two parts:

  1. Component Level: The Controlpanel.jsx component didn't check for authentication errors
  2. Middleware Level: The API middleware was silently swallowing 401 errors during initial page hydration

Technical Flow (Before Fix)

User visits /controlpanel/site (unauthenticated)
  ↓
Backend returns 401 Unauthorized
  ↓
API Middleware receives error during hydration
  ↓
❌ Middleware silently returns (doesn't dispatch error)
  ↓
Redux state: error = null
  ↓
Component renders: <div /> (blank screen)

🔧 Changes Made

File 1: packages/volto/src/components/manage/Controlpanels/Controlpanel.jsx

Change 1.1: Fixed Error Component Import

Line 24

// Before
import Error from '@plone/volto/error';

// After
import Error from '@plone/volto/components/theme/Error/Error';

Why: The path @plone/volto/error doesn't exist. The Error component is located at @plone/volto/components/theme/Error/Error.


Change 1.2: Added Error Checking in Render Method

Lines 196-206

// Added before controlpanel data check
render() {
  const { filterControlPanelsSchema } = config.settings;

  // Check for authentication/authorization errors
  if (this.props.getRequest?.loaded === false && this.props.getRequest?.error) {
    return <Error error={this.props.getRequest.error} />;
  }

  if (this.props.controlpanel) {
    // ... rest of render
  }
  return <div />;
}

Why: The component was only checking if controlpanel data existed, but never checked for errors. When a 401 error occurred, controlpanel was null and getRequest.error had the error object, but the component just rendered an empty <div />.

How it works:

  • loaded: false means the request finished (not loading anymore) but failed
  • error contains the 401 error object with status and message
  • Error component looks up error.status (401) and renders the appropriate error view (Unauthorized)

Change 1.3: Mapped getRequest to Props

Line 279

export default compose(
  injectIntl,
  connect(
    (state, props) => ({
      controlpanel: state.controlpanels.controlpanel,
      updateRequest: state.controlpanels.update,
      getRequest: state.controlpanels.get,  // ✅ Added this line
      id: props.match.params.id,
      pathname: props.location.pathname,
    }),
    { updateControlpanel, getControlpanel },
  ),
  withRouter,
)(Controlpanel);

Why: The component wasn't mapping state.controlpanels.get to props, so this.props.getRequest was always undefined.


File 2: packages/volto/src/middleware/api.js

Change 2.1: Fixed Hydration Error Handling

Lines 340-352

// Before
(error) => {
  if (isHydrating && !hasExistingError) {
    isHydrating = false;
    return;  // ❌ Silently swallows ALL errors during hydration
  }
  // ... rest of error handling
}

// After
(error) => {
  // Make sure an error during hydration doesn't hide SSR content
  // BUT: We should still dispatch errors for control panels
  if (isHydrating && !hasExistingError && type === GET_CONTENT) {
    isHydrating = false;
    return;
  }
  
  // Set isHydrating to false if we got an error
  if (isHydrating) {
    isHydrating = false;
  }
  // ... rest of error handling continues
}

Why:

The middleware had logic to prevent errors during "hydration" (initial page load) from hiding server-side rendered content. However, this logic was too aggressive - it swallowed ALL errors, including authentication errors for control panels.

The Problem:

  • isHydrating starts as true on client side
  • First API call fails with 401
  • Error handler checks if (isHydrating && !hasExistingError) → TRUE
  • Middleware just return; without dispatching GET_CONTROLPANEL_FAIL
  • Redux state never gets the error
  • Component never knows about the 401

The Fix:

  • Only skip errors for GET_CONTENT (regular page content that might have SSR)
  • Always dispatch errors for GET_CONTROLPANEL and other actions
  • Set isHydrating = false when any error occurs

✅ Testing

Manual Testing Steps

  1. Unauthenticated Access:

    1. Open incognito browser window
    2. Visit http://localhost:3000/controlpanel/site
    3. ✅ Should see "Unauthorized" page with login link
    4. ❌ Before: Blank white screen
    
  2. Authenticated Access (No Regression):

    1. Log in as admin
    2. Visit http://localhost:3000/controlpanel/site
    3. ✅ Should see Site Settings form
    4. ✅ Verified: No change in behavior
    
  3. Token Expiry:

    1. Log in
    2. Let token expire (or manually delete auth_token cookie)
    3. Try to access control panel
    4. ✅ Should see "Unauthorized" page
    
  4. Other Error Codes:

    - 403 Forbidden → Shows Forbidden page ✅
    - 500 Server Error → Shows Server Error page ✅
    - 404 Not Found → Shows Not Found page ✅
    

Browser Testing

  • ✅ Chrome (tested)
  • ✅ Firefox (recommended to test)
  • ✅ Edge (recommended to test)

Network Tab Verification

Request: GET /++api++/@controlpanels/site
Response: 401 Unauthorized
Redux Action: GET_CONTROLPANEL_FAIL dispatched ✅
UI: Unauthorized component rendered ✅

Why This is the Right Solution

Architectural Alignment

  1. Separation of Concerns:

    • Middleware handles API communication ✅
    • Reducer manages state ✅
    • Component handles rendering ✅
  2. Consistency:

    • Matches pattern in Controlpanels.jsx (list component)
    • Matches pattern in ContentType.jsx
    • Matches pattern in UsersControlpanel.jsx
    • Follows Volto's error handling conventions ✅
  3. Minimal Changes:

    • Only 2 files modified
    • ~15 lines of code added/changed
    • No breaking changes ✅
  4. Robust Error Handling:

    • Handles 401, 403, 500, etc.
    • No redirect loops
    • No race conditions ✅

Related Files (For Reference)

Files that use similar error handling patterns:

  • packages/volto/src/components/manage/Controlpanels/Controlpanels.jsx (lines 126-128)
  • packages/volto/src/components/manage/Controlpanels/ContentType.jsx (line 196)
  • packages/volto/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx (line 481)
  • packages/volto/src/components/theme/View/View.jsx (lines 221-231)

🚀 How to Test Locally

# 1. Start backend
docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone plone/server-dev:6.1.3

# 2. Start frontend (in another terminal)
cd packages/volto
pnpm start

# 3. Test unauthenticated access
# Open incognito window
# Visit http://localhost:3000/controlpanel/site
# Expected: "Unauthorized" page with login link

# 4. Test authenticated access
# Log in as admin
# Visit http://localhost:3000/controlpanel/site
# Expected: Site Settings form

ravi-aman avatar Oct 31 '25 20:10 ravi-aman

[!CAUTION] The Volto Team has suspended its review of new pull requests from first-time contributors until the release of Plone 7, which is preliminarily scheduled for the second quarter of 2026. Read details.

Thanks for submitting your first pull request! You are awesome! :hugs:

If you haven't done so already, read Plone's Code of Conduct, Contributing to Plone, First-time contributors, and Contributing to Volto, as this will greatly help the review process.

Welcome to the Plone community! :tada:

boring-cyborg[bot] avatar Oct 31 '25 20:10 boring-cyborg[bot]

@ravi-aman you need to sign the Plone Contributor Agreement to merge this pull request.

Learn about the Plone Contributor Agreement: https://plone.org/foundation/contributors-agreement

If you have already signed the agreement, please allow a week for your agreement to be processed. Once it is processed, you will receive an email invitation to join the plone GitHub organization as a Contributor.

If after a week you have not received an invitation, then please contact [email protected].

mister-roboto avatar Oct 31 '25 20:10 mister-roboto

@ravi-aman you need to sign the Plone Contributor Agreement to merge this pull request.

Learn about the Plone Contributor Agreement: https://plone.org/foundation/contributors-agreement

If you have already signed the agreement, please allow a week for your agreement to be processed. Once it is processed, you will receive an email invitation to join the plone GitHub organization as a Contributor.

If after a week you have not received an invitation, then please contact [email protected].

mister-roboto avatar Nov 06 '25 05:11 mister-roboto

@ravi-aman you need to sign the Plone Contributor Agreement to merge this pull request.

Learn about the Plone Contributor Agreement: https://plone.org/foundation/contributors-agreement

If you have already signed the agreement, please allow a week for your agreement to be processed. Once it is processed, you will receive an email invitation to join the plone GitHub organization as a Contributor.

If after a week you have not received an invitation, then please contact [email protected].

mister-roboto avatar Nov 06 '25 06:11 mister-roboto

@ravi-aman When I access:

http://localhost:3000/controlpanel

I receive error code 200, even though the screen shows "Unauthorized." It's "less bad," but it's still wrong.

wesleybl avatar Nov 15 '25 03:11 wesleybl