Securing Transports

Transporters provide a mechanism for bringing content into a wiki site. Here we record a possible way of restricting access to transporters so that they can only be accessed by the owner of the wiki that is accessing them.

### Making Transporter available in wiki domain.

There are three possible way of protecting transporters: - - Make the transporters available as a package that a wiki owner can install locally, and be available as localhost. - Use a proxy server in front of the transporters, and wiki, and make the transporters available within the wiki domain - this is the mechanism we will look at here. - Secure the transporters seperately from the wiki.

The idea behind making a group of transporters available with in the wiki uri namespace is so that the wiki session cookie will also be passed to the transporter. This is achieved using a reverse proxy, for this example we will be using an example wiki domain of `example.wiki`, and a simple echo transporter. There is nothing special in the wiki config, being something like:

{ "farm": true, ... whatever config is required for security ..., "wikiDomains": { "example.wiki": {} } }

We will expose the transports as a subdomain `transport.example.wiki`, which will allow it to act a foreign wiki server. However this will get the wiki users identity, without a modification to the transporter plugin which has other implications, so we will also expose the transporter on a subdirectory for when restricted access if required. In this example we will use `/transport`, so our example echo transporter will be available as `/transport/echo`.

Using Caddy 2, as an example proxy, our configuration would look like, tls config is not included:

transporter.example.wiki:443 { uri /transport/* strip_prefix /transport reverse_proxy localhost:8081 { header_up Host {host} } } example.wiki:443, *.example.wiki:443 { route /transport/* { uri /transport/* strip_prefix /transport reverse_proxy localhost:8081 { header_up Host {host} } } reverse_proxy localhost:3000 { header_up Host {host} } }

Using this example the echo plugin would be avaiable by using `POST /transport/echo/`.

To restrict access to the transporter to just the owner of the wiki that's making the transport request, the transporter code will need to modified to read: i) the origin of the wiki the request is coming from, ii) the identity cookie for the user making the request, and iii) use that information to check if they are indeed the wiki owner.

The full code of a sample echo transporter is available as gist , lets step though the code.

app.post('/echo', function (req, res) { // So lets restrict access to this to only the // owner of the wiki making the request. // // First we need to know which wiki the request was // made on, and the wikiSession of the person // making the request. So, lets extract the // referer and cookie from the request. console.log('/echo') var wikiOrigin = undefined var wikiHost = undefined var requestCookies = undefined if (req.headers.referer) { wikiOrigin = new URL(req.headers.referer).origin wikiHost = new URL(req.headers.referer).host } if (req.headers.cookie) { requestCookies = req.headers.cookie }

In this inital section we get the origin, and host, of the wiki the request is being made from, and the cookies from the request.

if (typeof wikiOrigin !== 'undefined' && typeof requestCookies !== 'undefined') { // we now know the origin of the wiki the // request was made on, and the user's cookies // so we can check that the user is the wiki // owner. // we can use the private proxy in the wiki // server to check if the user is the wiki // owner var url = wikiOrigin + `/proxy/${wikiHost}/welcome-visitors.json` // isWikiOwner makes a call using the private // proxy, it will return true if the user is // the owner, and false if they are not. const isWikiOwner = async url => { const response = await fetch(url, {'headers': { 'accept': '*/*', 'cookie': requestCookies } }) .then(function(response) { if (response.ok) return true }).catch(function(error) { return false }) }

As long as we have both the wikiOrigin and request cookies, we can proceed. The `url` is for making a proxy request from itself, this could be for any resource that will exist. The function `isWikiOwner` fetches the url, adding the cookies to the request. As long as the cookies identify the wiki owner the fetch will succeed, but if they are not it will return a `403`.

if (isWikiOwner(url)) { // the user is the wiki owner, so send the // response res.json({'title': 'Transport Parameters', 'story': [{'type': 'paragraph', 'id': random_id(), 'text': 'These are all of the parameters sent in the post body of the transport request.'}, {'type': 'code', 'id': random_id(), 'text': `${JSON.stringify(req.body, null, ' ')}`}], 'journal': [] }) } else { // we will get here if the check to see if the user is the wikiOwner fails. res.json({'title': 'Echo Transporter', 'story': [{'type': 'paragraph', 'id': random_id(), 'text': 'This transporter is only available to the wiki owner.'}, ], 'journal': [] }) } } else { // we will get here if the request does not // include the referer or any cookies res.json({'title': 'Echo Transporter', 'story': [{'type': 'paragraph', 'id': random_id(), 'text': 'This transporter is only available to the wiki owner.'}, ], 'journal': [] }) } })

We check if the user is the wiki owner, by calling `isWikiOwner`. If they are we perform the transporter, in this case a simple echo of data passed to the transporter. If they are not, or if we didn't get the information required to check, a simple error page is returned.