prove_keybase 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -0,0 +1,67 @@
1
+ # ProveKeybase
2
+ [![Travis CI](https://travis-ci.org/keybase/prove_keybase.svg?branch=master)](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
+
@@ -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,5 @@
1
+ class ProveKeybase::ConfigController < ProveKeybase::KeybaseBaseController
2
+ def show
3
+ render json: ProveKeybase.configuration, serializer: ProveKeybase::ConfigSerializer
4
+ end
5
+ 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,2 @@
1
+ class ProveKeybase::BaseJob < ActiveJob::Base
2
+ 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,3 @@
1
+ class ProveKeybase::BaseRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ 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,9 @@
1
+ class ProveKeybase::SerializableUser
2
+ include ActiveModel::Serialization
3
+ attr_accessor :keybase_proofs, :avatar_url
4
+
5
+ def initialize(keybase_proofs, avatar_url)
6
+ @keybase_proofs = keybase_proofs
7
+ @avatar_url = avatar_url
8
+ end
9
+ 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 %>
@@ -0,0 +1,5 @@
1
+ ProveKeybase::Engine.routes.draw do
2
+ get 'config', to: 'config#show'
3
+ resources :proofs, only: [:new, :create]
4
+ get 'api/v1/proofs/:username', to: 'api_v1_proofs#show', as: :check_proof
5
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ create initializer and db migration for keybase proof integration
3
+
4
+ Example:
5
+ bin/rails g prove_keybase install
6
+
7
+ This will create:
8
+ config/initializers/prove_keybase.rb
9
+ db/migrate/{X}_create_keybase_proofs.rb
@@ -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,9 @@
1
+ module ProveKeybase
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ProveKeybase
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ end
8
+ end
9
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProveKeybase
4
+ VERSION = '0.1.0'.freeze
5
+ 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: []