Repository: evennia/evennia

Issue:
Web client doesn't work behind TLS-enabled reverse proxy.
#### Brief summary of issue / Description of requested feature:
Web client doesn't work behind TLS-enabled reverse proxy. I was unable to find this issue being duplicated on any forums or on the issue tracker. It works fine when not being accessed through the reverse proxy.

#### Steps to reproduce the issue / Reasons for adding feature:

1. Set up NGINX reverse proxy at server 1 with reverse proxy, TLS enabled

2.  Install game server/portal on server 2, then try to access the web client.

3. ???

4. Don't profit, because it doesn't work.

#### Error output / Expected result of feature
Blank output at web client interface. 

#### Extra information, such as Evennia revision/repo/branch, operating system and ideas for how to solve / implement:

EDIT:
Current solution: Fallback to AJAX by disabling Websocket-enable as recommended by @chainsol .



Discussion:
Can confirm this. Workaround is disable Websocket-enabled webclient - it'll fallback to AJAX.
Currently working on [my development server](https://dev.perfectdisorder.net)
EDIT: This is done by adding one line to settings.py:
'''
WEBSOCKET_CLIENT_ENABLED = False
'''

EDIT 2: The reason this happens is the websocket link is hardcoded in the webclient, and it uses a non-secure websocket ( ws://url ), thus modern browsers disallow that connection. I've attempted to forward through the proxy specifically to the non-secure websocket - but I haven't been able to make that work.
I was able to get a TLS reverse proxy working with websockets by doing the following:

1. Update [base.html](https://github.com/evennia/evennia/blob/master/evennia/web/webclient/templates/webclient/base.html#L46-L50) to look like below (removed the `:{{websocket_port}}` part inside of `if` block.):
```
{% if websocket_url %}
    var wsurl = "{{websocket_url}}";
{% else %}
    var wsurl = "ws://" + this.location.hostname + ":{{websocket_port}}";
{% endif %}
```
2. Then, inside of my `server/conf/settings.py`, I set `WEBSOCKET_CLIENT_URL` to be `wss://external-hostname.com/ws` which points to my TLS reverse proxy server.
3. Finally, in my reverse proxy server, I configured it to proxy any `/ws` requests to `ws://127.0.0.1:4005/`.

If everyone thinks this is acceptable, I would be happy to submit a pull request updating `base.html` and the docs.
Exciting! I'll try that out! If that works it seems like a great idea!
@bostrt Could you go more into detail on your reverse proxy setup? I've tried in Apache, but it doesn't seem too happy with proxying websockets for whatever reason.

Edit: Also, are you certain it's working? If you go to the web console/inspector, is there a message about the websocket connection failing and a fallback to AJAX? That's what happens to me, but I get a 503 (service unavailable), when I proxy to localhost:4005.
Yes, seems to work for me fine. I see the websocket request receiving a `101` status with the `Upgrade: websocket` response header. Here's my httpd configuration:

```
<VirtualHost *:80>
  ServerName external.example.com
  Redirect / https://external.example.com
</VirtualHost>

<VirtualHost *:443>
  SSLEngine On
  ServerName external.example.com
  SSLCertificateFile /etc/pki/tls/certs/external.example.com.crt
  SSLCertificateKeyFile /etc/pki/tls/private/external.example.com.key
  SSLProtocol TLSv1.2
  SSLCipherSuite HIGH:!eNULL:!NULL:!aNULL
  ProxyPass /ws ws://127.0.0.1:4005/
  ProxyPassReverse /ws ws://127.0.0.1:4005/
  ProxyPass / http://127.0.0.1:4001/
  ProxyPassReverse / http://127.0.0.1:4001/

  ErrorLog logs/evennia_error.log
  CustomLog logs/evennia_access.log combined
</VirtualHost>
```

Then, in my `/home/mud/ainneve/server/conf/settings.py` I added the folllowing. Especially note that `/ws` addition; this is key in allowing httpd to differentiate between plain HTTP requests and the websocket requests without the need for a `RewriteRule`. Also, make sure to have the change to `base.html` I mentioned in https://github.com/evennia/evennia/issues/1421#issuecomment-350586031 :

```
WEBSOCKET_CLIENT_URL = "wss://external.example.com/ws"
```
Finally, here's my javascript console output when connecting to the webclient. No failure mentioned there:

```
evennia.js:447 {"0":"Trying websocket ..."}
evennia.js:447 {"0":"Evennia initialized."}
```
Oh! Also, if you are using SELinux, make sure to allow httpd to make the outgoing proxy connection:

```
# setsebool httpd_can_network_connect on
```
OK, I can confirm this as working with your modifications! Something was off about my Evennia instance, who knows what, but a fresh instance and the changes you describe and we're off to the races - Evennia's webclient with SSL! As long as @Griatch thinks that change makes sense I think you've hit the nail on the head!
@bostrt, @chainsol This looks like a good way to resolve it. A PR with the base.html update along with a wiki update describing the proxy would be highly useful I think. 

Resolving patch:
diff --git a/evennia/settings_default.py b/evennia/settings_default.py
--- a/evennia/settings_default.py
+++ b/evennia/settings_default.py
@@ -89,9 +89,9 @@
 WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0'
 # Actual URL for webclient component to reach the websocket. You only need
 # to set this if you know you need it, like using some sort of proxy setup.
-# If given it must be on the form "ws://hostname" (WEBSOCKET_CLIENT_PORT will
-# be automatically appended). If left at None, the client will itself
-# figure out this url based on the server's hostname.
+# If given it must be on the form "ws[s]://hostname[:port]". If left at None, 
+# the client will itself figure out this url based on the server's hostname.
+# e.g. ws://external.example.com or wss://external.example.com:443
 WEBSOCKET_CLIENT_URL = None
 # This determine's whether Evennia's custom admin page is used, or if the
 # standard Django admin is used.


Test patch: