The Desktop App exercises relatively strict control over the user’s ability to navigate through the web. This is done for a few reasons:
The Mattermost Web App is self-contained, with the majority of links provided by react-router
and thus most navigation is handled by that module. However, in the Desktop App, we have a major feature that allows users to navigate between distinct tabs bound to the same server. There are two ways that this style of navigation happens in the Web App:
react-router
Link
componentbrowserHistory.push
directly within the Web App based on the user action
Both of these methods will make use of the browserHistory
module within the Web App.When one of the above methods is used, normally the Web App would update the browser’s URL and change the state of the page. In the Desktop App, we instead send the arguments of the call to browserHistory.push
up to the Electron Main Process. The information is received at the method WindowManager.handleBrowserHistoryPush
, where we perform the following actions:
http://server-1.com/mattermost
, any path that is received will start with /mattermost
and we will need to remove that component. The same would be true for any other path following the origin http://server-1.com
.http://server-1.com/mattermost
, if the pathname is /mattermost/boards/board1
, we would get the Boards view matching the server.For the cases where a user wants to navigate away from the Web App to an external site, we generally want to direct the user outside of the Desktop App and have them open their default web browser and use the external site in that application.
In order to achieve this, we need to explicitly handle every other link and method of navigation that is available to an Electron renderer process. Fortunately, Electron provides a few listeners that help us with that:
window.location.hash
.target=_blank
. We attach this listener using the setWindowOpenHandler
and will allow us to allow
or deny
the opening as we desire.In our application, we define all of these listeners in the webContentEvents
module, and we attach them whenever a new webContents object is create to make sure that all renderer processes are correctly secured and set up correctly.
Our new window handler will deny the opening of a new Electron window if any of the following cases are true:
http
, https
, and any other protocol that was explicitly allowed by the user).
/api/v4/public/files/*
)/api/v4/image/*
)/help/*
)There are two cases where we do allow the application to open a new window:
devtools:
protocol, so that we can open the Chrome Developer Tools./plugins/*
). In these cases we allow a single popup per tab to be opened for certain plugins to do things like OAuth (e.g. GitHub or JIRA).Any other case will be automatically denied for security reasons.
By default, the Mattermost Web App marks any link external to its application as target=_blank
, so that the application doesn’t try to open it in the same window. Any other links should therefore be internal to the application.
We deny any sort of in-window navigation with the following exceptions: if the link is a mailto:
link (which always opens the default mail program), OR if we are in the custom login flow.
In order to facilitate logging into to the app using an external provider (e.g. Okta) in the same way that one would in the browser, we add an exception to the navigation flow that bypasses the will-navigate
check.
When a user clicks on a login link that redirects them to a matching URL scheme (listed here), we will activate the custom login flow. The URL MUST still be internal to the application before we activate this flow, or any URL matching this pattern would allow the app to circumvent the navigation protection.
While the current window is in the custom login flow, all links that emit the will-navigate
event will be allowed. Anything that opens a new window will still be restricted based on the rules for new windows. We leave the custom login flow once the app has navigated back to an URL internal to the application