prove_keybase 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +27 -0
- data/README.md +67 -0
- data/Rakefile +28 -0
- data/app/controllers/prove_keybase/api_v1_proofs_controller.rb +9 -0
- data/app/controllers/prove_keybase/config_controller.rb +5 -0
- data/app/controllers/prove_keybase/keybase_base_controller.rb +36 -0
- data/app/controllers/prove_keybase/proofs_controller.rb +45 -0
- data/app/jobs/prove_keybase/base_job.rb +2 -0
- data/app/jobs/prove_keybase/update_from_keybase_job.rb +11 -0
- data/app/models/prove_keybase/base_record.rb +3 -0
- data/app/models/prove_keybase/keybase_proof.rb +22 -0
- data/app/models/prove_keybase/serializable_user.rb +9 -0
- data/app/serializers/prove_keybase/config_serializer.rb +55 -0
- data/app/serializers/prove_keybase/user_serializer.rb +12 -0
- data/app/views/prove_keybase/proofs/new.html.erb +18 -0
- data/config/routes.rb +5 -0
- data/lib/generators/prove_keybase/USAGE +9 -0
- data/lib/generators/prove_keybase/prove_keybase_generator.rb +13 -0
- data/lib/generators/prove_keybase/templates/initializer.rb +54 -0
- data/lib/generators/prove_keybase/templates/migration.rb +19 -0
- data/lib/prove_keybase.rb +15 -0
- data/lib/prove_keybase/configuration.rb +29 -0
- data/lib/prove_keybase/engine.rb +9 -0
- data/lib/prove_keybase/is_keybase_provable.rb +14 -0
- data/lib/prove_keybase/keybase_adapter.rb +78 -0
- data/lib/prove_keybase/keybase_client.rb +38 -0
- data/lib/prove_keybase/version.rb +5 -0
- data/lib/tasks/prove_keybase_tasks.rake +7 -0
- metadata +183 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 98de567e2e93c7858d52f3ddc970d034353a0222666537e2148403d9929a2d23
|
4
|
+
data.tar.gz: b19bb531849068ecc423ea074c5b6c35957551133dafa147a5a034779830a978
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7504350b2e10e01a0136c765a34e32b01e51d3aeef0b51a5fd42bccdab0dbc519aa596a3f832a01df5207732904e1a728c1398a0a5cc7be9336a73f4cfba37cf
|
7
|
+
data.tar.gz: 00db06417b4fb949fce1d6a0324c0882190f132aa2697b8c41d9b2524c3ffd195bfc52d149a2fe8f34045cf5c7329fdf5e844314149941f10cb1f6807a6e1645
|
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2019, Keybase
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of Keybase nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# ProveKeybase
|
2
|
+
[](https://travis-ci.org/keybase/prove_keybase)
|
3
|
+
|
4
|
+
This is a drop-in implementation of Keybase's open proof protocol for Ruby on Rails. Here is a generic [implementation guide](https://keybase.io/docs/proof_integration_guide) and [announcement](https://keybase.io/blog/keybase-proofs-for-mastodon-and-everyone) for the protocol.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
This documentation assumes that `User` is the model and table off of which `keybase_proofs` will hang. If this is not the case for your app, just replace accordingly.
|
9
|
+
|
10
|
+
1. Add the gem and `bundle install`
|
11
|
+
```ruby
|
12
|
+
gem 'prove_keybase'
|
13
|
+
```
|
14
|
+
2. Run the generator to create the migration for a new table and an initializer file.
|
15
|
+
```bash
|
16
|
+
bundle exec rails generate prove_keybase install
|
17
|
+
```
|
18
|
+
3. Edit those two files for your site details. The comments in those files will guide you through it.
|
19
|
+
4. Add `is_keybase_provable` to your User model like this:
|
20
|
+
```ruby
|
21
|
+
class User < ApplicationRecord
|
22
|
+
is_keybase_provable
|
23
|
+
end
|
24
|
+
```
|
25
|
+
5. Add the engine to your routes file like this:
|
26
|
+
```ruby
|
27
|
+
mount ProveKeybase::Engine => "/prove_keybase"
|
28
|
+
```
|
29
|
+
6. Override some of the behavior to customize. This step is not optional. See below.
|
30
|
+
7. Validate your hosted config against Keybase to see if everything is pulling through correctly:
|
31
|
+
```bash
|
32
|
+
curl https://keybase.io/_/api/1.0/validate_proof_config.json\?config_url\=https://#{YOUR-SITE.COM}/prove_keybase/config.json
|
33
|
+
> {"status":{"code":0,"name":"OK"}}
|
34
|
+
```
|
35
|
+
8. Add keybase proofs to your users' profiles. [Here](spec/dummy/app/views/users/show.html.erb) is an ugly example of this in the dummy app. You can also check out how this looks on other sites that have implemented the protocol.
|
36
|
+
9. Send a keybase chat message to `@xgess` or `@mlsteele` to validate and flip on the integration from the Keybase side. If you message before this point, one of them can also help you test the whole thing end-to-end.
|
37
|
+
|
38
|
+
## Overrides
|
39
|
+
|
40
|
+
### Definitely do this
|
41
|
+
First and foremost, the view template to create a new proof is ugly as sin. This is intentional. Please make it look and feel like it's part of your site, which it is. For an example of how to do this, [here](app/views/prove_keybase/proofs/new.html.erb) is the view you need to override, and [here](spec/dummy/app/views/prove_keybase/proofs/new.html.erb) it is being overridden in the dummy app.
|
42
|
+
|
43
|
+
### Consider doing this
|
44
|
+
A lot of the controller behavior, especially around handling authentication, is constructed in this gem with subclassing / method-overriding in mind. Take a look at the [KeybaseBaseController](app/controllers/prove_keybase/keybase_base_controller.rb). All of these methods _can_ (and some _should_) be overridden. The dummy app has an example of this [here](spec/dummy/app/controllers/keybase_base_controller.rb).
|
45
|
+
|
46
|
+
## Keeping data up-to-date
|
47
|
+
It's always possible for a user to revoke their proof in Keybase. To ensure that your site is not linking to a broken proof, we have a couple of suggestions. The behavior that checks Keybase for a proof and updates your `keybase_proofs` table is accessible through an async job called `UpdateFromKeybaseJob`. Keybase would prefer if you did not call this every time every user sees another user's proofs. A nice compromise is to fire this off whenever a user looks at their own proofs. We do this in the dummy app [here](spec/dummy/app/controllers/users_controller.rb).
|
48
|
+
|
49
|
+
There is also a rake task in the gem that explains how you might consider updating all of the proofs at the same time.
|
50
|
+
```
|
51
|
+
bundle exec rake prove_keybase:update_proofs
|
52
|
+
```
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
Normal stuff. Run the tests:
|
57
|
+
```bash
|
58
|
+
bundle exec rake
|
59
|
+
```
|
60
|
+
And if you have access to a locally running set of Keybase servers, I recommend setting up a proxy for the dummy app e.g. `ngrok http 3001` and running it like this
|
61
|
+
```
|
62
|
+
PORT=3001 KEYBASE_BASE_URL=http://localhost:3000 KEYBASE_MY_DOMAIN=8f9e4887.ngrok.io be rails s
|
63
|
+
```
|
64
|
+
|
65
|
+
## License
|
66
|
+
The gem is available as open source under the terms of this [license](./LICENSE).
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ProveKeybase'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rspec/core/rake_task'
|
25
|
+
|
26
|
+
RSpec::Core::RakeTask.new(:spec)
|
27
|
+
|
28
|
+
task :default => :spec
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class ProveKeybase::ApiV1ProofsController < ProveKeybase::KeybaseBaseController
|
2
|
+
def show
|
3
|
+
proofs = ProveKeybase::KeybaseProof.where(username: params[:username])
|
4
|
+
serializable_user = ProveKeybase::SerializableUser.new(proofs, avatar_url_from_username(params[:username]))
|
5
|
+
render json: serializable_user, serializer: ProveKeybase::UserSerializer
|
6
|
+
rescue ActiveRecord::RecordNotFound
|
7
|
+
render json: {}, status: 404
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ProveKeybase::KeybaseBaseController < ::ApplicationController
|
4
|
+
protect_from_forgery with: :exception
|
5
|
+
|
6
|
+
def user_is_logged_in!
|
7
|
+
handle_login_redirect unless current_user
|
8
|
+
end
|
9
|
+
|
10
|
+
def user_proving_own_account!
|
11
|
+
unless current_user.username.casecmp(@proving_username).zero?
|
12
|
+
handle_wrong_logged_in_user(current_user.username, @proving_username)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle_proof_failed
|
17
|
+
flash[:alert] = 'proof failed. please start again from keybase.'
|
18
|
+
redirect_to new_proof_url(params: proof_params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle_wrong_logged_in_user(logged_in_as, proving)
|
22
|
+
flash[:alert] = "you're logged in as #{logged_in_as} but trying to prove #{proving}"
|
23
|
+
handle_login_redirect
|
24
|
+
end
|
25
|
+
|
26
|
+
def avatar_url_from_username(username)
|
27
|
+
# if you override this method, please keep the behavior
|
28
|
+
# of raising an exception when a user does not exist.
|
29
|
+
User.find_by!(username: username).avatar_url || 'https://example.com/default_avatar.jpg'
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle_login_redirect
|
33
|
+
redirect_to Rails.application.routes.url_helpers.send(ProveKeybase.configuration.login_redirection)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ProveKeybase::ProofsController < ProveKeybase::KeybaseBaseController
|
4
|
+
before_action :user_is_logged_in!
|
5
|
+
before_action :set_proving_username
|
6
|
+
before_action :user_proving_own_account!
|
7
|
+
|
8
|
+
def new
|
9
|
+
@proof = current_user.keybase_proofs.new(
|
10
|
+
username: params[:username],
|
11
|
+
kb_username: params[:kb_username],
|
12
|
+
token: params[:token]
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
@proof = current_user.keybase_proofs.where(
|
18
|
+
username: proof_params[:username],
|
19
|
+
kb_username: proof_params[:kb_username]
|
20
|
+
).first_or_initialize
|
21
|
+
@proof.token = proof_params[:token]
|
22
|
+
|
23
|
+
if @proof.save
|
24
|
+
@proof.refresh
|
25
|
+
redirect_to @proof.on_success_path(proof_params[:kb_ua])
|
26
|
+
else
|
27
|
+
handle_proof_failed
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def set_proving_username
|
34
|
+
case params[:action]
|
35
|
+
when 'create'
|
36
|
+
@proving_username = proof_params[:username]
|
37
|
+
when 'new'
|
38
|
+
@proving_username = params[:username]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def proof_params
|
43
|
+
params.require(:proof).permit(:username, :kb_username, :token, :kb_ua)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class ProveKeybase::UpdateFromKeybaseJob < ProveKeybase::BaseJob
|
2
|
+
queue_as ProveKeybase.configuration.job_queue
|
3
|
+
|
4
|
+
retry_on KeyError
|
5
|
+
retry_on Faraday::Error
|
6
|
+
retry_on ProveKeybase::ExpectedProofLiveError, wait: 1.second, attempts: 10
|
7
|
+
|
8
|
+
def perform(proof_id)
|
9
|
+
ProveKeybase::KeybaseProof.find(proof_id).refresh!
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ProveKeybase::KeybaseProof < ProveKeybase::BaseRecord
|
4
|
+
self.table_name = 'keybase_proofs'
|
5
|
+
|
6
|
+
validates :token, format: { with: /\A[a-f0-9]+\z/ }, length: { is: 66 }
|
7
|
+
validate :validate_with_keybase, if: :token_changed?
|
8
|
+
|
9
|
+
scope :active, -> { where(proof_valid: true, proof_live: true) }
|
10
|
+
|
11
|
+
delegate :on_success_path, :keybase_avatar_url, :profile_url,
|
12
|
+
:proof_url, :badge_url, :refresh, :refresh!,
|
13
|
+
to: :remote_adapter
|
14
|
+
|
15
|
+
def remote_adapter
|
16
|
+
@remote_adapter ||= ::ProveKeybase::KeybaseAdapter.new(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_with_keybase
|
20
|
+
remote_adapter.validate!
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ProveKeybase::ConfigSerializer < ActiveModel::Serializer
|
4
|
+
attributes :version, :domain, :display_name, :username,
|
5
|
+
:brand_color, :logo, :description, :prefill_url,
|
6
|
+
:profile_url, :check_url, :check_path, :avatar_path,
|
7
|
+
:contact
|
8
|
+
|
9
|
+
def version
|
10
|
+
1
|
11
|
+
end
|
12
|
+
|
13
|
+
def logo
|
14
|
+
{ svg_black: object.logo_svg_black,
|
15
|
+
svg_full: object.logo_svg_full }
|
16
|
+
end
|
17
|
+
|
18
|
+
def username
|
19
|
+
{ min: object.user_min_length,
|
20
|
+
max: object.user_max_length,
|
21
|
+
re: object.user_re }
|
22
|
+
end
|
23
|
+
|
24
|
+
def prefill_url
|
25
|
+
params = {
|
26
|
+
kb_username: '%{kb_username}',
|
27
|
+
username: '%{username}',
|
28
|
+
token: '%{sig_hash}',
|
29
|
+
kb_ua: '%{kb_ua}'
|
30
|
+
}
|
31
|
+
generate_url(:new_proof_url, params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_url
|
35
|
+
params = { username: '%{username}' }
|
36
|
+
generate_url(:check_proof_url, params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_path
|
40
|
+
['signatures']
|
41
|
+
end
|
42
|
+
|
43
|
+
def avatar_path
|
44
|
+
['avatar']
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def generate_url(route, params)
|
50
|
+
opts = params.merge(host: ProveKeybase.configuration.domain_for_urls, protocol: 'https')
|
51
|
+
CGI.unescape(
|
52
|
+
ProveKeybase::Engine.routes.url_helpers.send(route, opts)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class ProveKeybase::UserSerializer < ActiveModel::Serializer
|
2
|
+
attributes :avatar_url
|
3
|
+
has_many :keybase_proofs, key: :signatures
|
4
|
+
|
5
|
+
class KeybaseProofSerializer < ActiveModel::Serializer
|
6
|
+
attributes :sig_hash, :kb_username
|
7
|
+
|
8
|
+
def sig_hash
|
9
|
+
object.token
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<p id='alert'><%= alert %></p>
|
2
|
+
|
3
|
+
<h2>Override This View Please</h2>
|
4
|
+
|
5
|
+
<p>this is you here:</p>
|
6
|
+
<p><%= image_tag current_user.avatar_url, width: 200 %></p>
|
7
|
+
|
8
|
+
<p>is this you on keybase?</p>
|
9
|
+
<p><%= image_tag @proof.keybase_avatar_url, width: 200 %></p>
|
10
|
+
|
11
|
+
|
12
|
+
<%= form_with scope: :proof, url: proofs_path, local: true do |f| %>
|
13
|
+
<%= f.hidden_field :token, value: @proof.token %>
|
14
|
+
<%= f.hidden_field :username, value: @proof.username %>
|
15
|
+
<%= f.hidden_field :kb_username, value: @proof.kb_username %>
|
16
|
+
<%= f.hidden_field :kb_ua, value: params[:kb_ua] %>
|
17
|
+
<%= submit_tag 'yes' %>
|
18
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class ProveKeybaseGenerator < Rails::Generators::NamedBase
|
2
|
+
include Rails::Generators::Migration
|
3
|
+
source_root File.expand_path('templates', __dir__)
|
4
|
+
|
5
|
+
def install
|
6
|
+
copy_file "initializer.rb", "config/initializers/prove_keybase.rb"
|
7
|
+
migration_template "migration.rb", "db/migrate/create_keybase_proofs.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.next_migration_number(dir)
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# See https://keybase.io/docs/proof_integration_guide for more details on
|
4
|
+
# how this integration works.
|
5
|
+
ProveKeybase.setup do |config|
|
6
|
+
# These two values are how users will lookup your site on Keybase
|
7
|
+
config.domain = 'yoursite.com'
|
8
|
+
config.display_name = 'Bee Activists'
|
9
|
+
|
10
|
+
# In the unlikely event that your site is actually running on a different
|
11
|
+
# domain (perhaps a subdomain, or for testing/staging purposes) you might
|
12
|
+
# want to specify that the URLs are actually on a different domain. This
|
13
|
+
# will default to `config.domain` above, so you can safely ignore this.
|
14
|
+
# config.domain_for_urls = 'yoursite.com'
|
15
|
+
|
16
|
+
# This is the URL to which a Keybase user will be linked when they click
|
17
|
+
# on a proof of one of your users. Please leave `%{username}` for interpolation
|
18
|
+
# by Keybase. This page should link back to Keybase when a user has active
|
19
|
+
# proofs.
|
20
|
+
config.profile_url = "https://yoursite.com/users/%{username}"
|
21
|
+
|
22
|
+
config.description = 'Next gen social network using big data & AI in the cloud 🤖☁️.'
|
23
|
+
config.brand_color = '#282c37'
|
24
|
+
|
25
|
+
# Keybase will use these as an early pre-check before creating the proof and signature
|
26
|
+
config.user_min_length = 2
|
27
|
+
config.user_max_length = 30
|
28
|
+
config.user_re = '^[a-zA-Z0-9_]{2,30}$'
|
29
|
+
|
30
|
+
# A full color SVG. Should look good at 32px square. Expand all texts and strokes to shapes.
|
31
|
+
# Here's an example of a good one.
|
32
|
+
config.logo_svg_full = 'https://keybase.io/images/paramproofs/services/gubble.cloud/logo_full.svg'
|
33
|
+
|
34
|
+
# A full-black monochrome SVG. Should look good at 16px square. Expand all texts and strokes to shapes.
|
35
|
+
# Here's an example of a good one.
|
36
|
+
config.logo_svg_black = 'https://keybase.io/images/paramproofs/services/gubble.cloud/logo_black.svg'
|
37
|
+
|
38
|
+
# So Keybase has someone to reach out to if there are any issues
|
39
|
+
config.contact = ['dummy@yoursite.com', 'beezkneez@keybase']
|
40
|
+
|
41
|
+
# After creating a proof locally, there is an async task to check that Keybase has seen it and updated
|
42
|
+
# accordingly. The queue on which this is running can be configured here.
|
43
|
+
# config.job_queue = :default
|
44
|
+
|
45
|
+
# This is the route_helper method to which a user will be redirected who is
|
46
|
+
# trying to create a proof without being logged in, or if they're logged in
|
47
|
+
# as the wrong user. See the readme for details on overriding this behavior
|
48
|
+
# if you'd like more control over the experience. Ideally, if a user is
|
49
|
+
# redirected, they will be sent back to the correct page after logging in.
|
50
|
+
config.login_redirection = :new_signin_path
|
51
|
+
|
52
|
+
# recommend putting this image (or similar) in your asset pipeline and updating this
|
53
|
+
config.default_keybase_avatar_url = 'https://keybase.io/images/icons/icon-keybase-logo-64@2x.png'
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateKeybaseProofs < ActiveRecord::Migration[5.2]
|
2
|
+
def change
|
3
|
+
create_table :keybase_proofs do |t|
|
4
|
+
|
5
|
+
# If `User` is not the correct model, update this line
|
6
|
+
t.belongs_to :user, foreign_key: { on_delete: :cascade }
|
7
|
+
|
8
|
+
t.string :username, null: false, default: ''
|
9
|
+
t.string :kb_username, null: false, default: ''
|
10
|
+
t.text :token, null: false, default: ''
|
11
|
+
t.boolean :proof_valid
|
12
|
+
t.boolean :proof_live
|
13
|
+
|
14
|
+
t.timestamps null: false
|
15
|
+
end
|
16
|
+
|
17
|
+
add_index :keybase_proofs, [:username, :kb_username], unique: true, name: :index_kb_proofs_on_local_user
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'active_record'
|
3
|
+
require 'active_model_serializers'
|
4
|
+
require 'faraday'
|
5
|
+
require 'faraday_middleware'
|
6
|
+
|
7
|
+
require 'prove_keybase/engine'
|
8
|
+
require 'prove_keybase/is_keybase_provable'
|
9
|
+
require 'prove_keybase/keybase_adapter'
|
10
|
+
require 'prove_keybase/keybase_client'
|
11
|
+
require 'prove_keybase/configuration'
|
12
|
+
|
13
|
+
ActiveSupport.on_load(:active_record) do
|
14
|
+
include ProveKeybase::Model
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ProveKeybase
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.setup
|
7
|
+
self.configuration ||= Configuration.new
|
8
|
+
yield(configuration)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Configuration
|
12
|
+
include ActiveModel::Serialization
|
13
|
+
|
14
|
+
attr_accessor :domain, :display_name, :description, :brand_color,
|
15
|
+
:user_min_length, :user_max_length, :user_re, :logo_svg_full,
|
16
|
+
:logo_svg_black, :profile_url, :contact, :keybase_base_url,
|
17
|
+
:default_keybase_avatar_url, :job_queue, :login_redirection,
|
18
|
+
:domain_for_urls
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
# defaults
|
22
|
+
@job_queue = :default
|
23
|
+
@login_redirection = :root_path
|
24
|
+
@default_keybase_avatar_url = 'https://keybase.io/images/icons/icon-keybase-logo-64@2x.png'
|
25
|
+
@keybase_base_url = ENV.fetch('KEYBASE_BASE_URL') { 'https://keybase.io' }
|
26
|
+
@domain_for_urls = @domain
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ProveKeybase
|
2
|
+
module Model
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def is_keybase_provable
|
9
|
+
model_class = self
|
10
|
+
model_class.has_many :keybase_proofs, class_name: 'ProveKeybase::KeybaseProof'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class ProveKeybase::ExpectedProofLiveError < StandardError; end
|
2
|
+
|
3
|
+
class ProveKeybase::KeybaseAdapter
|
4
|
+
def initialize(proof)
|
5
|
+
@proof = proof
|
6
|
+
@domain = ProveKeybase.configuration.domain
|
7
|
+
@base_url = ProveKeybase.configuration.keybase_base_url
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate!
|
11
|
+
if keybase_valid?
|
12
|
+
@proof.proof_valid = true
|
13
|
+
else
|
14
|
+
@proof.errors.add(:base, 'token not valid for user combo in keybase')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def refresh
|
19
|
+
ProveKeybase::UpdateFromKeybaseJob.perform_later(@proof.id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def refresh!
|
23
|
+
status = client.remote_status
|
24
|
+
|
25
|
+
# if Keybase thinks the proof is valid but not live, yet the proof exists locally,
|
26
|
+
# then this is very likely during the creation flow, and Keybase just hasn't
|
27
|
+
# fetched the proof through the API yet. Throw this specific error so we know to
|
28
|
+
# retry.
|
29
|
+
raise ProveKeybase::ExpectedProofLiveError if status[:proof_valid] && !status[:proof_live]
|
30
|
+
|
31
|
+
@proof.update!(status.slice(:proof_valid, :proof_live))
|
32
|
+
end
|
33
|
+
|
34
|
+
def keybase_valid?
|
35
|
+
client.proof_valid?
|
36
|
+
end
|
37
|
+
|
38
|
+
def keybase_live?
|
39
|
+
client.proof_live?
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_success_path(kb_ua)
|
43
|
+
uri = URI.parse(File.join(@base_url, '/_/proof_creation_success'))
|
44
|
+
uri.query = URI.encode_www_form(proof_params.merge(kb_ua: kb_ua || 'unknown'))
|
45
|
+
uri.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def keybase_avatar_url
|
49
|
+
client.pic_url
|
50
|
+
end
|
51
|
+
|
52
|
+
def proof_url
|
53
|
+
File.join(@base_url, "#{@proof.kb_username}/sigchain\##{@proof.token}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def profile_url
|
57
|
+
File.join(@base_url, @proof.kb_username)
|
58
|
+
end
|
59
|
+
|
60
|
+
def badge_url
|
61
|
+
File.join(@base_url, "#{@proof.kb_username}/proof_badge/#{@proof.token}?username=#{@proof.username}&domain=#{@domain}")
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def proof_params
|
67
|
+
{
|
68
|
+
domain: @domain,
|
69
|
+
username: @proof.username,
|
70
|
+
kb_username: @proof.kb_username,
|
71
|
+
sig_hash: @proof.token
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def client
|
76
|
+
@client ||= ProveKeybase::KeybaseClient.new(proof_params, @base_url)
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class ProveKeybase::KeybaseClient
|
2
|
+
def initialize(proof_params, base_url)
|
3
|
+
@proof_params = proof_params
|
4
|
+
api_url = File.join(base_url, '/_/api/1.0/')
|
5
|
+
@api_conn = Faraday::Connection.new(url: api_url) do |faraday|
|
6
|
+
faraday.response :json
|
7
|
+
faraday.adapter :net_http
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def pic_url
|
12
|
+
res = @api_conn.get('user/pic_url.json', username: @proof_params[:kb_username]).body
|
13
|
+
res.fetch('pic_url')
|
14
|
+
rescue NoMethodError, KeyError, Faraday::Error
|
15
|
+
ProveKeybase.configuration.default_keybase_avatar_url
|
16
|
+
end
|
17
|
+
|
18
|
+
def proof_valid?
|
19
|
+
res = @api_conn.get('sig/proof_valid.json', @proof_params).body
|
20
|
+
res.fetch('proof_valid', false)
|
21
|
+
rescue Faraday::Error
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def proof_live?
|
26
|
+
res = @api_conn.get('sig/proof_live.json', @proof_params).body
|
27
|
+
res.fetch('proof_live', false)
|
28
|
+
rescue Faraday::Error
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def remote_status
|
33
|
+
# allow network and unexpected response errors to bubble up
|
34
|
+
res = @api_conn.get('sig/proof_live.json', @proof_params).body
|
35
|
+
{ proof_valid: res.fetch('proof_valid'),
|
36
|
+
proof_live: res.fetch('proof_live') }
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
namespace :prove_keybase do
|
2
|
+
desc 'Queue a job to update from Keybase for every keybase proof'
|
3
|
+
task :update_proofs do
|
4
|
+
puts 'We recommend implementing this especially for your site and running it from time to time.'
|
5
|
+
puts 'It might be as easy as `ProveKeybase::KeybaseProof.all.find_each(&:refresh)` though.'
|
6
|
+
end
|
7
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prove_keybase
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Gessner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: active_model_serializers
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday_middleware
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bcrypt
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: travis
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Add the Keybase open protocol for identity proofs to your Rails app
|
126
|
+
email:
|
127
|
+
- alex@keyba.se
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- LICENSE
|
133
|
+
- README.md
|
134
|
+
- Rakefile
|
135
|
+
- app/controllers/prove_keybase/api_v1_proofs_controller.rb
|
136
|
+
- app/controllers/prove_keybase/config_controller.rb
|
137
|
+
- app/controllers/prove_keybase/keybase_base_controller.rb
|
138
|
+
- app/controllers/prove_keybase/proofs_controller.rb
|
139
|
+
- app/jobs/prove_keybase/base_job.rb
|
140
|
+
- app/jobs/prove_keybase/update_from_keybase_job.rb
|
141
|
+
- app/models/prove_keybase/base_record.rb
|
142
|
+
- app/models/prove_keybase/keybase_proof.rb
|
143
|
+
- app/models/prove_keybase/serializable_user.rb
|
144
|
+
- app/serializers/prove_keybase/config_serializer.rb
|
145
|
+
- app/serializers/prove_keybase/user_serializer.rb
|
146
|
+
- app/views/prove_keybase/proofs/new.html.erb
|
147
|
+
- config/routes.rb
|
148
|
+
- lib/generators/prove_keybase/USAGE
|
149
|
+
- lib/generators/prove_keybase/prove_keybase_generator.rb
|
150
|
+
- lib/generators/prove_keybase/templates/initializer.rb
|
151
|
+
- lib/generators/prove_keybase/templates/migration.rb
|
152
|
+
- lib/prove_keybase.rb
|
153
|
+
- lib/prove_keybase/configuration.rb
|
154
|
+
- lib/prove_keybase/engine.rb
|
155
|
+
- lib/prove_keybase/is_keybase_provable.rb
|
156
|
+
- lib/prove_keybase/keybase_adapter.rb
|
157
|
+
- lib/prove_keybase/keybase_client.rb
|
158
|
+
- lib/prove_keybase/version.rb
|
159
|
+
- lib/tasks/prove_keybase_tasks.rake
|
160
|
+
homepage: https://keybase.io
|
161
|
+
licenses:
|
162
|
+
- BSD-3-Clause
|
163
|
+
metadata: {}
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 2.3.0
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubygems_version: 3.0.1
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Add the Keybase open protocol for identity proofs to your Rails app
|
183
|
+
test_files: []
|