Skip to content

Conversation

mistercrunch
Copy link
Member

@mistercrunch mistercrunch commented Sep 23, 2025

Summary

The Flask flash message system is a legacy feature from when Superset was a server-rendered application. In the current SPA architecture, these messages are never displayed to users because:

  • Flash messages only render on page load via FlashProvider's componentDidMount
  • Modern UI interactions use async API calls that don't trigger page reloads
  • The main consumer (/explore/ POST endpoint) is already marked @deprecated

All user-facing notifications are already handled by the frontend toast system, including chart save operations (see saveModalActions.ts) which dispatches success toasts for:

  • Chart saved/overwritten
  • Chart added to dashboard
  • New dashboard created with chart

Changes

Backend

  • Removed all flash() calls from views (14 occurrences in 4 files)
  • Converted error flashes to JSON responses or logging
  • Removed redirect_with_flash utility function
  • Fixed open redirect vulnerability in dashboard access denial

Frontend

  • Deleted FlashProvider component and tests
  • Removed flash_messages from CommonBootstrapData type
  • Cleaned up context providers and test fixtures
  • Removed unused getBootstrapData imports

Security Fixes

  • Fixed open redirect vulnerability by using url_for() instead of request.url
  • Dashboard access denial now uses safe URL construction

Code Cleanup

  • Removed unnecessary pass statements and comments
  • Converted permalink errors to JSON responses for consistency
  • Verified no tests depend on flash functionality

BREAKING CHANGE

Removes flask.flash() messaging infrastructure. However, no actual functionality is lost as the frontend already handles all notifications through its own toast system.

TESTING INSTRUCTIONS

  1. Save a chart from Explore - verify toast notifications appear
  2. Add chart to dashboard - verify success message
  3. Try accessing a dashboard without permissions - verify proper redirect
  4. Run frontend tests: npm test
  5. Run backend tests: pytest

ADDITIONAL INFORMATION

The application now relies entirely on client-side toast notifications for user feedback, which properly support async operations and provide a consistent UX.

@github-actions github-actions bot added the api Related to the REST API label Sep 23, 2025
@dosubot dosubot bot added change:backend Requires changing the backend change:frontend Requires changing the frontend labels Sep 23, 2025
Copy link

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

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

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Performance Redundant string conversion in logging ▹ view 🧠 Not in scope
Files scanned
File Path Reviewed
superset-frontend/src/components/index.ts
superset-frontend/src/explore/types.ts
superset-frontend/src/constants.ts
superset/views/auth.py
superset-frontend/src/views/RootContextProviders.tsx
superset-frontend/src/types/bootstrapTypes.ts
superset-frontend/src/embedded/EmbeddedContextProviders.tsx
superset-frontend/src/dashboard/actions/hydrate.js
superset/views/utils.py
superset/views/base.py
superset/views/core.py

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

BREAKING CHANGE: Removes flask.flash() messaging infrastructure

The Flask flash message system is a legacy feature from when Superset was a
server-rendered application. In the current SPA architecture, these messages
are never displayed to users because:
- Flash messages only render on page load via FlashProvider's componentDidMount
- Modern UI interactions use async API calls that don't trigger page reloads
- The main consumer (/explore/ POST endpoint) is already marked @deprecated

All user-facing notifications are already handled by the frontend toast system,
including chart save operations (see saveModalActions.ts) which dispatches
success toasts for:
- Chart saved/overwritten
- Chart added to dashboard
- New dashboard created with chart

Changes:
- Backend: Removed all flash() calls from views (14 occurrences in 4 files)
- Backend: Converted error flashes to JSON responses or logging
- Backend: Removed redirect_with_flash utility function
- Backend: Fixed open redirect vulnerability in dashboard access denial
- Frontend: Deleted FlashProvider component and tests
- Frontend: Removed flash_messages from CommonBootstrapData type
- Frontend: Cleaned up context providers and test fixtures
- Frontend: Removed unused getBootstrapData imports

Security fixes:
- Fixed open redirect vulnerability by using url_for() instead of request.url
- Dashboard access denial now uses safe URL construction

Code cleanup:
- Removed unnecessary pass statements and comments
- Converted permalink errors to JSON responses for consistency
- Verified no tests depend on flash functionality

The application now relies entirely on client-side toast notifications for
user feedback, which properly support async operations and provide a
consistent UX.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
superset_can_csv: findPermission('can_csv', 'Superset', roles),
common: {
// legacy, please use state.common instead
flash_messages: common?.flash_messages,
Copy link
Member

Choose a reason for hiding this comment

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

Question from review session: Where are server-side messages being processed/summoned now that they're not provided as part of the bootstrap data?

Copy link
Member Author

@mistercrunch mistercrunch Sep 24, 2025

Choose a reason for hiding this comment

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

Answer is case by case, but short answer is "as they should" from async calls. Checked that most had been re-implemented the right way since they were used. Also confirmed that old mechanics are to surface "on the next page load", which has very few pre-determined triggers, stuff like logging in maybe.

if not reg:
logger.error(LOGMSG_ERR_SEC_NO_REGISTER_HASH, activation_hash)
flash(as_unicode(self.false_error_message), "danger")
logger.error("Registration activation failed: %s", self.false_error_message)
Copy link
Member

Choose a reason for hiding this comment

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

@mistercrunch this is one of the errors that used to be (rightly) surfaced to users, but now might silent in the UI. These (and other?) server-side messages might no longer appear on the frontend.

Copy link
Member

Choose a reason for hiding this comment

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

Relayed: We just need to make sure we have a frontend counterpart for all server side messages that were deleted.

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me confirm that logging in is a SPA operation as it should be as opposed to a backend-round-trip.

Copy link
Member Author

Choose a reason for hiding this comment

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

Login screen is the only place where this seems to actually work (round-trip + timely flash). Let me refactor this nonsense!

Copy link
Member Author

@mistercrunch mistercrunch Sep 24, 2025

Choose a reason for hiding this comment

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

Mitigated with a toast, but had to keep the round trip (same as now) at least for now

After removing Flask's flash message system, login errors weren't being displayed.
This adds a temporary solution using sessionStorage and toast notifications to show
'Invalid username or password' when login fails.

Implementation:
- Set a flag in sessionStorage before form submission
- On page reload, check if flag exists (indicates failed login)
- Show error toast and clear password field for security
- Added TODO comment noting this should be replaced with proper SPA auth

This addresses reviewer concerns about missing login error messages while keeping
the solution simple since it will eventually be replaced with a JSON API approach.
self.pre_delete(item)
except Exception as ex: # pylint: disable=broad-except
flash(str(ex), "danger")
logger.error("Pre-delete error: %s", str(ex))
Copy link
Member Author

Choose a reason for hiding this comment

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

this probably never should have gone through to user

_("Error: permalink state not found"), status=404
)
except (ChartNotFoundError, ExplorePermalinkGetFailedError) as ex:
flash(__("Error: %(msg)s", msg=ex.message), "danger")
Copy link
Member Author

Choose a reason for hiding this comment

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

function flagged as @deprecated

initial_form_data["slice_id"] = slice_id
if form_data_key:
flash(
_("Form data not found in cache, reverting to chart metadata.")
Copy link
Member Author

Choose a reason for hiding this comment

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

part of a function flagged as @deprecated

if form_data_key:
flash(
_(
"Form data not found in cache, reverting to dataset metadata." # noqa: E501
Copy link
Member Author

Choose a reason for hiding this comment

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

part of a function flagged as @deprecated

)

flash(
_("Chart [{}] was added to dashboard [{}]").format(
Copy link
Member Author

Choose a reason for hiding this comment

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

tested that chart adding to dashboard is handled frontend now and flashed properly without these obsolete flash messages

)
flash(
_(
"Dashboard [{}] just got created and chart [{}] was added to it"
Copy link
Member Author

Choose a reason for hiding this comment

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

tested that chart adding to dashboard is handled frontend now and flashed properly without these obsolete flash messages

return func(database, user, schema, sql)


def redirect_with_flash(url: str, message: str, category: str) -> Response:
Copy link
Member Author

Choose a reason for hiding this comment

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

not used anymore

)
except SupersetSecurityException:
# Return 404 to avoid revealing dashboard existence
abort(404)
Copy link
Member Author

Choose a reason for hiding this comment

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

404 is most accurate when you don't have access

Copy link
Member

Choose a reason for hiding this comment

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

Yep... it's even in the official spec for 403 (I got curious):

An origin server that wishes to "hide" the current existence of a forbidden target resource MAY instead respond with a status code of 404 (Not Found).

Copy link
Member

@rusackas rusackas left a comment

Choose a reason for hiding this comment

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

LGTM!

mistercrunch and others added 3 commits September 24, 2025 17:54
…rmation leakage

- Dashboard access denial now returns 404 instead of redirecting
- Prevents attackers from enumerating which dashboards exist
- Updated tests to expect 404 instead of 302 for access denial
- 18 of 20 RBAC tests now pass (2 have teardown issues)

Co-Authored-By: Claude <[email protected]>
Using abort(404) inside an exception handler causes issues:
- Raises werkzeug.exceptions.NotFound which may not be caught properly
- Can leave database transactions in inconsistent state
- Causes test teardown failures

Solution: Return make_response('Not Found', 404) which returns a proper
Flask Response object with 404 status code.

All 20 RBAC security tests now pass.

Co-Authored-By: Claude <[email protected]>
Cleaner solution for returning 404 in the dashboard access denial case:
- Response(status=404) returns an empty response with proper status
- Simpler than make_response('Not Found', 404)
- Lets browser/client handle 404 appropriately
- All tests still pass

Co-Authored-By: Claude <[email protected]>
@mistercrunch mistercrunch merged commit abc2d46 into master Sep 25, 2025
73 of 74 checks passed
@mistercrunch mistercrunch deleted the test-env branch September 25, 2025 07:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Related to the REST API change:backend Requires changing the backend change:frontend Requires changing the frontend preset-io size/L
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants