We're having some problems configuring Aphlict for our site that uses reverse SSL proxies for access to Phabricator. It appears that Phabricator does not correctly convert the `notification.client-uri` scheme (to wss:// instead of ws://) in this case. I'll attach a proposed patch, but it requires some discussion.
In our setup, we have an internal machine called `hawaii` hosting Phabricator over HTTP. This machine is never accessed directly. Instead, we have configured a machine with an external IP address and hostname, say `phabricator.example.org`, which runs nginx listening for requests over HTTPS and forwarding them to the HTTP port. This is (part of) the Nginx configuration:
```
server {
listen 443 ssl;
[...SSL parameters...]
server_name phabricator.example.org;
location / {
proxy_set_header Host phabricator.example.org;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Front-End-Https on;
proxy_pass http://hawaii:8095;
}
}
```
This machine is also supposed to relay WebSockets traffic to the Phabricator instance. Therefore, we configured the Aphlict server to listen on client port 22280 and admin port 22281, set the `notification.client-uri` to `https://localhost/ws/` and the `notification.server-uri` to `http://localhost:22281`. We added (amongst others) the following block of Nginx configuration to the reverse SSL proxy:
```
upstream websocket_pool {
ip_hash;
server hawaii:22280;
}
server {
[..other directives as above..]
location = /ws/ {
proxy_pass http://websocket_pool;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Front-End-Https on;
proxy_read_timeout 9999999999;
}
}
```
Even though we could now succesfully open a WebSocket and send messages via `https://phabricator.example.org/ws/` that ended up in the Aphlict logs, the Phabricator main page could not connect. Eventually, I traced this down to the `config.websocketURI` being set to `ws://phabricator.example.org/ws/` instead of `wss://phabricator.example.org/ws`; hotfixing this in `Aphlict.js` "fixed" the problem and made the notification server work beautifully.
Tracing it further, this (seemingly) wrong choice was made in `src/view/page/PhabricatorStandardPageView.php`. Here, `notification.client-uri` is read and the protocol is set to either "ws" or "wss". However, this choice is made based on whether the current connection, according to Phabricator, is HTTPS. It seems to me that this choice should depend on the scheme of `notification.client-uri` (i.e. `$client_uri->getProtocol() == "https"`), not on the current connection. And, making things even more interesting, the current connection *was* actually over HTTPS, but `AprontRequest.php` only checks `$_SERVER['HTTPS']` and not the standard `X-Forwarded-Proto` / `Front-End-Https` headers. It's possible that this HTTPS choice is made incorrectly in more places because of this.
The attached patch fixes this problem by making the choice of scheme in `$client_uri` based on the current scheme in `$client_uri`. The two scenarios where this may break existing setups are:
* Current request is HTTP, `notification.client-uri` describes a `https://` URI but Aphlict doesn't use SSL. In that case, I wonder why `notification.client-uri` describes a HTTPS URI in the first place.
* Current request is HTTPS, `notification.client-uri` describes a `http://` URI and Aphlict uses SSL. This will probably occur in various places, as https://secure.phabricator.com/book/phabricator/article/notifications/ describes that the `notification.client-uri` can be set to `http://localhost/ws/` if this type of setup is used.
Another possible fix would be to change `AphrontRequest::isHTTPS` to look at the `X-Forwarded-Proto` or `Front-End-Https` headers as well:
* If current request is HTTPS but Aphront listens on HTTP, browser would not allow the connection currently as well, so we're not breaking anything by switching that to HTTPS
* If current request is HTTP, then you're on your own if you set headers indicating your connection is actually HTTPS if it's not.
It does not sound to me like the right solution, but it might prevent breakage for some users.