tzhf / chatguessr

A Twitch chatbot for GeoGuessr.
https://chatguessr.com
MIT License
41 stars 12 forks source link

Rework authentication to no longer send OAuth tokens #29

Closed ReAnnannanna closed 2 years ago

ReAnnannanna commented 2 years ago

From the early days of ChatGuessr, we told people to use an OAuth token generator to allow ChatGuessr to receive whispers. In v1.1.0-alpha.0 and v2.0.0, we also used this OAuth token to authenticate with ChatGuessr servers, which did this to verify identity:

const { data } = axios.get("https://id.twitch.tv/oauth2/validate", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});
const streamerUsername = data.login;

This approach is nice and simple, but it has a major problem. While we only use it for verifying identity, these OAuth tokens:

Both of these are very bad if we're sending them over the wire! We know that we don't abuse it but that is unproveable (even if the server was open source, there is no guarantee that the servers actually run that code and not something else entirely). And for users, trusting that an application on their computer is well-behaved is a different beast from trusting that this remote server is well-behaved and will be well-behaved into the future.

Using these tokens was very convenient for the ChatGuessr application in the past because it didn't really have to deal with authentication that much, it could just assume that the thing was valid and run with it. We could avoid a lot of the complexity that a login system typically requires. But now that we have to authenticate with our own servers, that complexity is no longer optional.

This patch reworks it so Twitch tokens are never sent. It uses Supabase just like the ChatGuessr map does, and uses the Supabase access token for authentication instead of the Twitch OAuth token. This means:

Because the tokens are only briefly valid, we need to go through the authentication flow every time we try to connect to chat to get a fresh token. This is done in a separate window that stays hidden as long as user interaction is not required.

This implementation is still a bit janky and I would like to improve it (eg. by pulling all the authentication code into a class in its own module). Different parts of the authentication flow are spread out across the codebase too much. However it's more important to get it out the door than to perfect it.

I tried to put most of the Twitch-specific code into a TwitchBackend class here, in the future we could add different Backend classes for other streaming services supported by Supabase, or even things like Discord.