ShadowKat Studios
Blog
Projects
About

Anonymous webchat with Prosody

Something I’ve felt to be lacking in the XMPP ecosystem is something resembling IRC webchat. No more!

IRC webchat

If you’ve ever dealt with an internet community - especially those to do with Minecraft, you’ve probably used IRC at one point or another. IRC has a lot of nice features, among those that you don’t need an account of any sort to join a channel, rooms are ephemeral, and the protocol can be implemented exceedingly easily. It also has some significant downsides, like the lack of support for channel history, dependence on external services for file sharing, and the inability to persist sessions between different connections.

But by far the neatest part of the new IRC user experience is webchat. You can click on a link you found or were sent, put in a name, and you’re in the channel.

IRC webchat, after clicking a link, but before joining the channel.

IRC webchat, after joining the channel in the URL.

XMPP MUCs

With XMPP, one normally has to have an account before joining a channels, or really doing anything. These can be on a server run by yourself, the server the MUC you’re joining is on, or on a public XMPP server, but you need an account somewhere.

The Join Channel dialog in Gajim, an XMPP client.

Besides that, XMPP is better in most ways. The protocol is more complicated, but has provisions for extension. There’s real support for multiple simultaneous connections, and unreliable connections like mobile devices. You can configure channels - Multi-User Chats, or MUCs, in XMPP parlance - to have as much or as little history as you’d like. There’s a standard way to share files. There’s even end-to-end encryption with support for file sharing. And as the cherry on top, it’s easy on your phone’s battery thanks to Client State Indication.

I’m greedy and want all of these upsides together, both from IRC and XMPP.
I could use Biboumi to bridge XMPP to IRC - hell, I already do for accessing IRC on my phone. That isn’t a very elegant solution though.

Prosody, ConverseJS, and mod_auth_anonymous

Prosody is a lightweight, easy to configure XMPP server written in Lua. It’s proven reliable enough for me to keep using it for the last 7 years, and if I need to make a modification, I don’t even need to recompile it.

Additionally, I’ve had a ConverseJS web interface set up for when I’m away from computers I control and want to log into XMPP, using the Prosody mod_conversejs addon.

Something I hadn’t played with before was the auth_anonymous plugin for Prosody. It provides an authentication method requiring no password and allocating a random username. Any server running mod_auth_anonymous has server-to-server connections disabled by default.

Some assembly required

I’m going to assume you already have a working Prosody install. If not, see the Prosody documentation on the subject.

Prosody

First, the “easy” part, configuring Prosody.
I added the following to my /etc/prosody.cfg.lua in order to configure a virtual host with anonymous authentication and a short file expiry time:

VirtualHost "anon.shadowkat.net"  
    authentication = "anonymous"  
    http_external_url = "https://social.shadowkat.net/anon///"  
    http_upload_expire_after = 60*60*24

Note that you don’t actually have to create a DNS record or get a valid certificate for this host. What you do need to do is, if you’re using a web server as a reverse proxy, make sure your webserver is sending the correct HTTP Host header.

My nginx configuration for social.shadowkat.net is as follows:

    location /xmpp/ {  
        proxy_set_header Host "shadowkat.net";  
        proxy_set_header X-Forwarded-For $remote_addr;  
        proxy_buffering off;  
        tcp_nodelay on;  
        proxy_pass   http://prosody.sks.local:5280/;  
    }  
    location /anon/ {  
        proxy_set_header Host "anon.shadowkat.net";  
        proxy_set_header X-Forwarded-For $remote_addr;  
        proxy_buffering off;  
        tcp_nodelay on;  
        proxy_pass   http://prosody.sks.local:5280/;  
    }

If you are, instead, using a separate HTTP File Upload component, you should already have that configured and you can remove the http_ lines from the anonymous virtual host entirely.

The last thing that needs to be done is to enable the mod_bosh Prosody module. This is well-documented on the Prosody page about setting up a BOSH server, including the reverse proxy configuration.

ConverseJS

Rather than futzing with Cross-Origin Resource Sharing (CORS), I decided to put a static page on the same domain as my BOSH endpoint to initialize ConverseJS. Because ConverseJS is written in Javascript, there’s no downside to using Javascript to start the ConverseJS client, so I wrote a short script to parse the URL parameters and start the client automatically.

 /* You can consider this bit of JS to be under GPLv3 or compatible licenses. */  
 function connect() {  
  var vars = {};  
  var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {  
   vars[key] = value;  
  });  
  if (typeof vars["channel"] !== 'undefined') {  
   var channel = vars["channel"] + "@social.shadowkat.net";  
   console.log(channel);  
   document.title = channel;  
   converse.initialize({  
    allow_logout: false,  
    allow_muc_invitations: false,  
    allow_contact_requests: false,  
    authentication: 'anonymous',  
    auto_login: true,  
    auto_join_rooms: [  
     channel,  
    ],  
    notify_all_room_messages: [  
     channel,  
    ],  
    nickname: vars["nick"],  
    bosh_service_url: 'https://social.shadowkat.net/xmpp/http-bind/',  
    jid: 'anon.shadowkat.net',  
    keepalive: true,  
    hide_muc_server: true,  
    muc_domain: "social.shadowkat.net",  
    locked_muc_domain: true,  
    auto_reconnect: true,  
    show_controlbox_by_default: false,  
    strict_plugin_dependencies: false,  
    singleton: true,  
    view_mode: "fullscreen",  
   });  
  }  
 }  
 connect()

You can view the current source at the webchat landing page.
The important parts to note are:

The rest is more or less cosmetic, and is explained in the ConverseJS configuration documentation. The end result of this is, when you open a URL with the channel as an argument, you’re greeted by this:

ConverseJS asking for a nickname to use in the channel you're about to join.

To top all this off, I wrapped it in a form configured to re-request the same page with the channel and username the user enters, links to alternative clients, and a warning if the user doesn’t have Javascript enabled.

A HTML form asking for a nickname and channel to join. A HTML form asking for a nickname and channel to join, but with a warning that the page won't work properly without Javascript.

Fully cooked.

The user has clicked a link, perhaps on a webpage. They’ve entered their nickname. What do they see?

get omemeo'd lol

By Izaya
2021-04-05 14:40 +1000

Tags: tech software privacy xmpp web

© ShadowKat Studios
The software used to generate this page is licensed under the Mozilla Public License version 2 and can be found here