Discourse Keybase Proof

We can implement the Keybase proof protocol, so people can add a proof of identity in the Discourse instances they participate.

https://keybase.io/blog/keybase-proofs-for-mastodon-and-everyone

27 Likes

I really like the idea of spearheading an implementation here, I know it is a rather advanced feature that only a handful of people will use, but it is good to support this kind of protocol and should be easy to implement.

I am placing #pr-welcome on this, @codinghorror would you like this slotted on 2.4 or 2.5 or just play it by ear?

Note… I would like to first see a plugin implementing the protocol prior to pulling into core.

13 Likes

I think #pr-welcome is good to start with, I support this, but it’s not like paying customers are asking for it or anything…

7 Likes

I suppose it could be implemented as a plugin, for starters.

2 Likes

keybase employee here. we actually just started on a gem for implementing the proof protocol. once done, it should be pretty easy to make a discourse plugin from it.

21 Likes

OK! Keybase gem for adding the open proof protocol to rails is here: https://github.com/keybase/prove_keybase

I poked a bit at the Discourse plugin system (very cool by the way), and I think someone who is familiar with it should have no trouble converting this gem. I’m available for any questions or help, and if you ping me (I’m on keybase @xgess) I can help get this turned on for a test or staging environment of yours with some fake accounts on our side.

16 Likes

I note that there have been lots of conversations in this forum over at least the last 4 years showing a strong desire for encrypted communications in Discourse. And they usually end up noting that it is a really hard problem, especially when encrypted group chats are considered, and that integrating with Keybase is a recommended alternative for a variety of reasons.

So I would advise those who really do want convenient, robust, encrypted communications to support this integration work, which is far easier than the work already in process, and does provide group chats and much more, all reliably tied to users based on their Discourse persona.

Here are a few references, starting with a wise quote from co-founder Sam Saffronsamco-founder about flaws with any web-based solution like Discourse:

Note that Keybase solves this by having native clients for many platforms.

Next up, on the current Discourse Encrypt project

Sam, would there be any way to extend this functionality to a category/threads (and allow by groups?)

Absolutely not, this is not in scope and not planned even 3 iterations out.

Next, the RFC for Discourse Encrypt which notes that they also don’t plan to properly address the multiple device or loss-of-key issues that Keybase is designed to get right:

the UI clearly explains that if she forgets this secret she will NEVER have access to her encrypted messages anymore

So again, please note that Keybase is now easy to integrate with, and that it solves problems that other solutions aren’t even planning to address.

5 Likes

Keybase integration into Discourse would be like Christmas for us.

2 Likes

I am writing a plugin to implement this :slight_smile: Since it’s my first attempt, I’d love some guidance.

This is what I am planning to do (feel free to suggest changes!):

  1. I will implement it as a plugin (I already have a GitHub repo at etamponi/discourse-keybase-proofs-plugin, it’s empty for now since I didn’t push my experiments yet).

  2. I think the only “full page” I need is the one for the prefill_url endpoint (the one that will show the association between the Discourse and the Keybase user). I didn’t look at examples from other sites but I think that it should be pretty easy and simple. This page should only be visible if the Discourse user to be associated is the one that’s currently logged in. If no user is logged in, I’ll redirect to the login page, possibly with the username prefilled in the login overlay. If the wrong user is logged in, I’ll display an error message.

  3. The plugin itself shouldn’t need any configuration option, since all the entries in the json configuration to be sent to Keybase can be computed from the Discourse settings.

  4. I’ll use an outlet in the user profile page to show the keybase badge/current association status. With the same approach, I’ll also show a control to remove the association from the Discourse side. Additionally, as suggested in the Keybase doc, when a user visits their own profile page, I’ll make a liveness check for the association.

  5. the rest of the endpoints will be JSON only.

Here are a couple questions:

  1. Where should I store the associations? Should I create a DB migration, or should I use the PluginStore?

  2. What would you suggest as the best way to present the user with the configuration to be sent to Keybase? I guess @kb_xgess might have some opinion on this!

Thanks in advance :slight_smile:

8 Likes

:fire: yes @emanuele. i’m happy to help. as for getting the config to keybase, i think the easiest for us is if each site hosts it at a stable path somewhere. that way all we need is the domain, and then we can fetch the config and turn it on from our side.

1 Like

That may be better as a sidekiq background job for Discourse.

Like every 24h, you check up to 100 users who haven’t been updated in the last 30 days.

(All numbers subject to tweaking).

I would go with a User custom field. Easy to get from a User object, and makes sense since association is per user.

4 Likes

Ok, I need to look at how to create a custom field :slight_smile: also, there can be more than one Keybase users linked to the same Discourse user (but not vice-versa), is a custom field still the best choice?

I’ll need to investigate about this too :slight_smile:

Here are some examples: https://github.com/discourse/discourse-signatures/blob/master/plugin.rb

You can store a JSON, which can be an object with keys for each Keybase user, or one array, etc.

Check https://github.com/discourse/discourse-patreon/blob/master/app/jobs/scheduled/patreon_sync_patrons_to_groups.rb

9 Likes

I am making progress :slight_smile: The new-proof flow is almost done, and the check flow should be quite easy to implement.

I am struggling to get the spec to run though. When I run bundle exec rake 'plugin:spec[keybase-proofs]', this is what I get:

LOAD_PLUGINS=1 /Users/etamponi/.rbenv/versions/2.6.2/bin/ruby -S rspec ./plugins/keybase-proofs/spec/requests/proof_controller_spec.rb --profile

Randomized with seed 1279
F

Failures:

  1) KeybaseProofs::ProofController can create a proof
     Failure/Error: expect(response.status).to eq(500)

       expected: 500
            got: 404

       (compared using ==)
     # ./plugins/keybase-proofs/spec/requests/proof_controller_spec.rb:11:in `block (2 levels) in <top (required)>'

Top 1 slowest examples (33.58 seconds, 99.9% of total time):
  KeybaseProofs::ProofController can create a proof
    33.58 seconds ./plugins/keybase-proofs/spec/requests/proof_controller_spec.rb:8

Finished in 33.62 seconds (files took 3.68 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./plugins/keybase-proofs/spec/requests/proof_controller_spec.rb:8 # KeybaseProofs::ProofController can create a proof

It would be way better for me if I could do some TDD at this point, to get the routes well written without manually go through the browser flow.

The repository is: https://github.com/etamponi/discourse-keybase-proofs-plugin

4 Likes

You’re using fetch_user_from_parameters

https://github.com/etamponi/discourse-keybase-proofs-plugin/blob/master/app/controllers/proof_controller.rb#L12

which will raise a 404 if the user cannot be found

https://github.com/discourse/discourse/blob/cbd4d06da0f4686d9eb0fb4e070fcf96ff47f145/app/controllers/application_controller.rb#L479

So you either need to add a username parameter to the request, or simply use current_user rather than relying on parameters.

7 Likes

:man_facepalming: :man_facepalming: :man_facepalming: Thanks for your hint! I had convinced myself that the 404 was coming from the router not seeing the plugin routes…

And also thanks for suggesting me to use current_user only, since I can make the “wrong user” check on the client side. Here in the backend, I would still need to validate the signature against KeyBase so it’s fine to not pass an username param, and the tests would be a bit easier to write too!

7 Likes

this is exciting! let me know if i can help reviewing the code or setting up a test domain on keybase’s side with some users so you can verify that it all works. and just a reminder to check out a bunch of ruby code we recently wrote as a generic rails gem to implement this behavior: https://github.com/keybase/prove_keybase. it handles a bunch of the weirder behavior you might not yet have encountered like (1) blocking attempts to prove a discourse account you don’t actually own, (2) sending the correct response codes when an existing user doesn’t have a proof vs that user not existing, …
thanks for doing this!

2 Likes

Thanks :slight_smile: I am reading through that plug-in extensively (and I’m using your implementation of KeybaseClient, almost unchanged :stuck_out_tongue: ) I was just going to ask you if you could point me at what should be the behaviour when the proof is not valid. I skimmed through the error handling code but, perhaps because of my lack of expertise with Rails, I missed the response code you are mentioning

3 Likes

sure thing. so a user starts from keybase and starts the proof creation flow. they get directed to discourse with the usernames and sig_hash (token) already populated (discourse checks that the user is logged in with the correct account: user_proving_own_account!), they confirm and hit OK or submit or whatever. At that point, discourse checks again that it’s the correct user, and tries to save the proof. as part of that save, discourse needs to ask keybase if the proof is valid. i think this is what you’re talking about. if the proof isn’t valid (which could mean they mangled the initial URL or they have already revoked the signature in keybase), it’ll bubble up and hit handle_proof_failed, which is basically just a flash notice and a redirect.

does that answer your question??

1 Like

Ok, so I should just redirect back to the new_proof_url endpoint, fair enough :slight_smile:

Another question. The server-side endpoint I wrote assumes that the logged-in user is the discourse user that is making the proof. Is this an error? I assumed this is ok because it would generate a “failed proof” error anyway, as the discourse username of the logged-in user and the username in the proof would not match.

Of course I am still doing the “user proving own account” part, but only on the client side, to show a nice message.

2 Likes