Subtopic: a secondary login API hack with bots

skeletal roles I took a look around the Discord server as it stood; not too many people were in it yet, and only a couple of superficial roles had been created and assigned.  For something about which my questions of weeks back had been answered with "don't worry, it's being handled", things obviously weren't very far along yet.  Time to tie in and get to work, there was a lot to build.


[I'm given to understand that Discord base ID strings with the "#nnnn" discriminator are somehow personally vulnerable info, not that I understand why other than it might enable unsolicited friend-requests, but I've obscured those throughout this report anyway.  Except for my own; I don't care about that, especially when everyone can see it on an avatar click anyways.]

  Roles and all could wait, though; the first task was to come up with some means of bringing in new users, setting their nicknames based on information from their "badgename" at the Registration site, and applying the "Arisian" role which would unlock the rest of the site for them.  Gail had spoken of having the online site generate a unique string, along with instructions to copy and paste it into some suitably "magic" area of Discord, as a possible strategy.

Or, the inverse.  It wasn't even clear which way the information had to flow, but the bottom line was that there was no automatic way to tie people with their own independent Discord accounts to their Arisia presence, without some means for them to simply tell us about it.  We weren't going to force anyone to create a new account just for this.  So some snippet of data would have to be "manually" carried between Discord and the Arisia backend, and databases suitably updated.  The backend wasn't my problem but I figured I could take something generated from there, and use it to perform the necessary functions on the Discord side.

See the Rathole: Discord API innards section later in the main flow for more on what we ultimately ran with.

Fortunately I already had been working on this sort of thing, and had a couple of successful experiments in parsing ordinary message typein to a channel and taking actions based on it.  A big part of most "bot" functionality is auto-moderation, where words and phrases considered "bad" can trigger actions, warnings, message deletion, user sanctions, etc.  The channel-content parsing has been greatly extended in many cases, enabling things like completely customized commands that can have the bot run little configurable mini-programs or scripts that Do Stuff.  The problem I now had was arbitrary parsing of arguments and plugging those back into another function, and the existing bot [YAGPDB, for "Yet Another General-Purpose Discord Bot"] seemed to have fairly obscure methods for handling that if it could at all.  To have an easier time of it, I brought in another bot called "Dynobot", which has a more straightforward way to handle arguments in custom commands.

[I also looked at MEE6, and gave up on it.  Not only does it keep spamming users about junk "levels", anything vaguely useful in it is limited by a paywall.  Eff that.]

limiting bot roles Many server operators are under the misimpression that bots have to have full administrative access and be at the top of the role stack.  This isn't true; they only need the privileges necessary to handle the functions we need them to do.  Sure, if they're going to assign roles to other users or set their nicknames they need the right bits, but the beauty of it is that there *are* specific permission bits for doing exactly those things!  Besides a set of base user permissions, bots all have their own role tag, which can also be used to allow or deny access to specific channels.

other people clearly work on the 'i agree...' parsing model too After seeing what other conventions set up so people could indicate their acceptance of Codes of Conduct and other conditions, I wanted to expand on that a bit.  The usual way was to simply click a reaction icon under an introductory message stating the rules, and a bot would grant a role to open up the rest of the site to the user.  I had already been playing with an enhanced mechanism, to ask a user to actually type a statement of acceptance and have that fact logged somewhere else.  Clearly, from various blog posts and Github pages I wasn't the only one to think about this, and a while back I had already built a working "I accept" proof of concept on my own little mini-server.  So parsing a magic string and granting a role was one half of it, and parsing more of the string for the required nickname-set was the other.

It turned out that having two different bots handle these pieces was the easiest way to go, at least at first.

gate tests full, pairs with regex2 yag-side, annotation for arg-parse mismatch Long story somewhat shorter, it wasn't that hard to make work.  The "code" on the Dyno end, if you can call it that, consisted simply of
    {!setnick {user} $2+}
which would take the remainder of a command string and make that the new nickname on the server.  This is from an early test; the running model was slightly different.  And it worked!  The semi-magic part was changing the "command prefix" to "--reg" instead of the usual ? or ! or whatever Dyno uses by default, to make it look more "registration"-ish oriented or the like.  But wait, the obscurity gets even better.

yag side: regex message parsing The role was handled on the YAGPDB side, where a slightly more rigorous check would happen and cleanup was easier.  The "automoderator v2" is just one step shy of fully custom commands, which I got into working with later in the con, but rules based on parsing channel input in a regular expression can do lots of things on its own.  This would watch for messages in a single channel, do the very simple validity check, set the "Arisian" for the user if it passed, and then delete the trigger message so it wouldn't be sitting there viewable in the channel by the next person to come along.  Pretty simple; it didn't care about the remainder of the string or the purported "badgename" other than being non-blank; handling that was Dyno's job.

thrashing gateway basics Back on the Arisia server, I could easily see the results of all these actions in the native audit log.  Now, it was time to formalize it a bit more and deploy it for real usability.

first go: what inbounds see at #portcullis I made a special "gateway" channel, gave the bots proper access to it, and limited the "magic string" functionality to just here so it would be ignored on any other channel.  Because of observed traditions seen in prior events, I also added a simple rolemenu -- an obvious "unlock" function, which would also give the role but not do anything about nicknames.  [See the rathole on "role menus" for more about how that works.]  We'd need this in the interim to simply get more people onto the server to begin with, and sort out the rest later.  So for the moment, this channel was dual-function.

Given what my text asked for and current events of the day, a possible "secret phrase to get what I want" was on everyone's mind so I illustrated trying it.  At the time [Jan. 5] it was ludicrous and laughable; the next day, it was utterly terrifying.

behind-the-scenes report Fortunately, I was able to turn this into a first entry in our idle-chatter tech channel and get amused reactions on it before things turned ugly.  At the time we were all too busy making the sausage to pay much attention.

direct chan URL access, limbo state A channel, and even a specific message, can be referenced by a heirarchy of web URLs in the form
where the three long numbers are guild, channel, and message-item respectively.  With my crash-dummy test user stripped of its Arisian role, I tried to directly access one of our restricted channels in that format.  I could see the channel name, but none of the content and certainly wouldn't be able to post anything.  Also, my visible server channel list at the left showed only the gateway, as intended.

This is just an interesting look at what you get by trying to bypass the normal client UI -- it doesn't really gain you anything.  If you try this against a server/guild you're not part of at all, you wind up in a weird limbo state.

#arrivals transition to pure logging The server was still set up to deliver internal system messages about user joins and such to a default #lobby channel that nobody really cared about, so I commandeered that and turned it private to be my running log of all kinds of events.  Dyno in particular has an interesting activity-logger function, which could make a lot of test actions very clear.  It would be useful to have this, for debugging and making sure other integrations were doing the same actions. 

usable 'hack login API' tests successful! Here's Dyno's log of another successful gatewaying-in test, showing me being granted the Arisian role, renamed to whatever I specified, and my message not only shown in full, but also summarily zapped afterward.  Hold my beer!

dyno log of portcullis tests This was also reflected at the Dyno configuration portal, where a backup log of custom-command use is kept.  If Dyno failed to deliver something important to our log channel, details could probably also be fished out of here if needed.

Not that I really expected a *human* problem so showstopping that we'd actually need all this "evidence".  One screenshot of someone actively being a dick would likely suffice.

webhook report Ancillary to messing with API-level stuff, which I was doing in a way but not directly, I also played with the "webhook" concept which is another kind of integration.  It is just used to deliver messages to a channel, *any* channel regardless of restrictions, because its "endpoint" URL gets set up with a token by an adminstrator in the first place.  I get into this a bit more in the "#pets" rathole.

By this time we were at the end of the next day, January 6 2021, a day which should bloody well "live in infamy" for a long time, and everyone was distracted by much more pressing issues.

_H*   210131