Add keybase integration (#661)
This commit is contained in:
parent
75750213da
commit
4fc391e97d
|
@ -38,6 +38,7 @@ public/apple-touch-icon*
|
||||||
# files added in production
|
# files added in production
|
||||||
lib/tasks/deploy.rake
|
lib/tasks/deploy.rake
|
||||||
config/initializers/production.rb
|
config/initializers/production.rb
|
||||||
|
config/initializers/development.rb
|
||||||
config/database.yml
|
config/database.yml
|
||||||
config/initializers/secret_token.rb
|
config/initializers/secret_token.rb
|
||||||
config/*.sphinx.conf
|
config/*.sphinx.conf
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path d="M0,0c0,5.33,0,10.67,0,16c5.33,0,10.67,0,16,0c0-5.33,0-10.67,0-16C10.67,0,5.33,0,0,0z M13.14,13.45c-3.2,0-6.43,0-9.63,0
|
||||||
|
c0-0.1,0-0.2,0-0.3c0-0.11,0-0.22,0-0.33c0-0.11,0-0.1,0.1-0.11c0.23-0.02,0.46-0.03,0.69-0.07c0.46-0.08,0.67-0.31,0.74-0.78
|
||||||
|
c0.02-0.14,0.03-0.29,0.03-0.43c0-2.25,0-4.5,0-6.74c0-0.18-0.02-0.36-0.04-0.53C4.99,3.85,4.79,3.66,4.5,3.55
|
||||||
|
c-0.27-0.1-0.55-0.11-0.83-0.13c-0.05,0-0.1,0-0.15,0c0-0.03,0-0.06,0-0.08c0-0.19,0-0.38,0-0.57c0-0.07,0.02-0.09,0.09-0.09
|
||||||
|
c1.04,0,2.07,0,3.11,0c0.71,0,1.42,0,2.13,0c0.07,0,0.1,0.01,0.1,0.09c-0.01,0.19,0,0.37,0,0.56c0,0.03,0,0.05,0,0.08
|
||||||
|
c-0.14,0.01-0.28,0-0.41,0.02c-0.2,0.03-0.4,0.06-0.59,0.11C7.6,3.65,7.42,3.89,7.38,4.22c-0.02,0.15-0.03,0.3-0.03,0.45
|
||||||
|
c0,2.13,0,4.26,0,6.39c0,0.27,0.03,0.54,0.06,0.8c0.03,0.29,0.22,0.48,0.49,0.58c0.23,0.09,0.48,0.11,0.72,0.12
|
||||||
|
c0.63,0.02,1.25,0.01,1.86-0.15c0.88-0.22,1.47-0.77,1.76-1.64c0.09-0.28,0.13-0.57,0.2-0.86c0.01-0.02,0.03-0.06,0.05-0.06
|
||||||
|
c0.23,0,0.42,0,0.66,0C13.11,11.06,13.16,12.25,13.14,13.45z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<g>
|
||||||
|
<path fill="#AC130D" d="M16,16c-5.33,0-10.67,0-16,0C0,10.67,0,5.33,0,0c5.33,0,10.67,0,16,0C16,5.33,16,10.67,16,16z"/>
|
||||||
|
<path fill="#FFFFFF" d="M3.5,13.45c0-0.1,0-0.2,0-0.3c0-0.11,0-0.22,0-0.33c0-0.11,0-0.1,0.1-0.11c0.23-0.02,0.46-0.03,0.69-0.07
|
||||||
|
c0.46-0.08,0.67-0.31,0.74-0.78c0.02-0.14,0.03-0.29,0.03-0.43c0-2.25,0-4.5,0-6.74c0-0.18-0.02-0.36-0.04-0.53
|
||||||
|
C4.99,3.85,4.79,3.66,4.5,3.55c-0.27-0.1-0.55-0.11-0.83-0.13c-0.05,0-0.1,0-0.15,0c0-0.03,0-0.06,0-0.08c0-0.19,0-0.38,0-0.57
|
||||||
|
c0-0.07,0.02-0.09,0.09-0.09c1.04,0,2.07,0,3.11,0c0.71,0,1.42,0,2.13,0c0.07,0,0.1,0.01,0.1,0.09c-0.01,0.19,0,0.37,0,0.56
|
||||||
|
c0,0.03,0,0.05,0,0.08c-0.14,0.01-0.28,0-0.41,0.02c-0.2,0.03-0.4,0.06-0.59,0.11C7.6,3.65,7.42,3.89,7.38,4.22
|
||||||
|
c-0.02,0.15-0.03,0.3-0.03,0.45c0,2.13,0,4.26,0,6.39c0,0.27,0.03,0.54,0.06,0.8c0.03,0.29,0.22,0.48,0.49,0.58
|
||||||
|
c0.23,0.09,0.48,0.11,0.72,0.12c0.63,0.02,1.25,0.01,1.86-0.15c0.88-0.22,1.47-0.77,1.76-1.64c0.09-0.28,0.13-0.57,0.2-0.86
|
||||||
|
c0.01-0.02,0.03-0.06,0.05-0.06c0.23,0,0.42,0,0.66,0c-0.02,1.2,0.02,2.4,0,3.6C9.94,13.45,6.71,13.45,3.5,13.45z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,73 @@
|
||||||
|
class KeybaseProofsController < ApplicationController
|
||||||
|
before_action :require_logged_in_user, only: [:new, :create]
|
||||||
|
before_action :check_new_params, only: :new
|
||||||
|
before_action :check_user_matches, only: :new
|
||||||
|
before_action :force_to_json, only: [:kbconfig]
|
||||||
|
|
||||||
|
def new
|
||||||
|
@kb_username = params[:kb_username]
|
||||||
|
@kb_signature = params[:kb_signature]
|
||||||
|
@kb_ua = params[:kb_ua]
|
||||||
|
@kb_avatar = Keybase.avatar_url(@kb_username)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
kb_username = post_params[:kb_username]
|
||||||
|
kb_signature = post_params[:kb_signature]
|
||||||
|
kb_ua = post_params[:kb_ua]
|
||||||
|
if Keybase.proof_valid?(kb_username, kb_signature, @user.username)
|
||||||
|
@user.add_or_update_keybase_proof(kb_username, kb_signature)
|
||||||
|
@user.save!
|
||||||
|
return redirect_to Keybase.success_url(kb_username, kb_signature, kb_ua, @user.username)
|
||||||
|
else
|
||||||
|
flash[:error] = "Failed to connect your account to Keybase. Try again from Keybase."
|
||||||
|
return redirect_to settings_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def kbconfig
|
||||||
|
return render json: {} unless Keybase.enabled?
|
||||||
|
@domain = Keybase.DOMAIN
|
||||||
|
@name = Rails.application.name
|
||||||
|
@brand_color = "#AC130D"
|
||||||
|
@description = "Computing-focused community centered around link aggregation and discussion"
|
||||||
|
@contacts = ["admin@#{Keybase.DOMAIN}"]
|
||||||
|
# rubocop:disable Style/FormatStringToken
|
||||||
|
@prefill_url = "#{new_keybase_proof_url}?kb_username=%{kb_username}&" \
|
||||||
|
"kb_signature=%{sig_hash}&kb_ua=%{kb_ua}&username=%{username}"
|
||||||
|
@profile_url = "#{u_url}/%{username}"
|
||||||
|
@check_url = "#{u_url}/%{username}.json"
|
||||||
|
# rubocop:enable Style/FormatStringToken
|
||||||
|
@logo_black = "https://lobste.rs/small-black-logo.svg"
|
||||||
|
@logo_full = "https://lobste.rs/full-color.logo.svg"
|
||||||
|
@user_re = User.username_regex_s[1...-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def force_to_json
|
||||||
|
request.format = :json
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_user_matches
|
||||||
|
unless case_insensitive_match?(@user.username, params[:username])
|
||||||
|
flash[:error] = "not logged in as the correct user"
|
||||||
|
return redirect_to settings_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def case_insensitive_match?(first_string, second_string)
|
||||||
|
# can replace this with first_string.casecmp?(second_string) when ruby >= 2.4.6
|
||||||
|
first_string.casecmp(second_string).zero?
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_params
|
||||||
|
params.require(:keybase_proof).permit(:kb_username, :kb_signature, :kb_ua, :username)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_new_params
|
||||||
|
redirect_to settings_path unless [:kb_username, :kb_signature, :kb_ua, :username].all? do |k|
|
||||||
|
params[k].present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
module KeybaseProofsHelper
|
||||||
|
def keybase_user_link(kb_sig)
|
||||||
|
File.join Keybase.BASE_URL, kb_sig[:kb_username]
|
||||||
|
end
|
||||||
|
|
||||||
|
def keybase_proof_link(kb_sig)
|
||||||
|
File.join Keybase.BASE_URL, kb_sig[:kb_username], "sigchain\##{kb_sig[:sig_hash]}"
|
||||||
|
end
|
||||||
|
end
|
|
@ -67,6 +67,7 @@ class User < ApplicationRecord
|
||||||
s.string :twitter_oauth_token
|
s.string :twitter_oauth_token
|
||||||
s.string :twitter_oauth_token_secret
|
s.string :twitter_oauth_token_secret
|
||||||
s.string :twitter_username
|
s.string :twitter_username
|
||||||
|
s.any :keybase_signatures, array: true
|
||||||
s.string :homepage
|
s.string :homepage
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -169,6 +170,10 @@ class User < ApplicationRecord
|
||||||
h[:twitter_username] = self.twitter_username
|
h[:twitter_username] = self.twitter_username
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.keybase_signatures.present?
|
||||||
|
h[:keybase_signatures] = self.keybase_signatures
|
||||||
|
end
|
||||||
|
|
||||||
h
|
h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -433,6 +438,12 @@ class User < ApplicationRecord
|
||||||
Time.current - self.created_at <= NEW_USER_DAYS.days
|
Time.current - self.created_at <= NEW_USER_DAYS.days
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_or_update_keybase_proof(kb_username, kb_signature)
|
||||||
|
self.keybase_signatures ||= []
|
||||||
|
self.keybase_signatures.reject! {|kbsig| kbsig['kb_username'] == kb_username }
|
||||||
|
self.keybase_signatures.push('kb_username' => kb_username, 'sig_hash' => kb_signature)
|
||||||
|
end
|
||||||
|
|
||||||
def is_heavy_self_promoter?
|
def is_heavy_self_promoter?
|
||||||
total_count = self.stories_submitted_count
|
total_count = self.stories_submitted_count
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<% if user.keybase_signatures? %>
|
||||||
|
Linked to
|
||||||
|
<% user.keybase_signatures.each do |kbs| %>
|
||||||
|
<strong><%= link_to "@#{kbs[:kb_username]}", keybase_user_link(kbs) %></strong>
|
||||||
|
<strong><%= link_to "✔", keybase_proof_link(kbs) %></strong>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
<% if for_self %>
|
||||||
|
(<%= link_to "Disconnect on Keybase", "https://keybase.io/download" %>)
|
||||||
|
<% end %>
|
||||||
|
<% elsif for_self %>
|
||||||
|
Install <%= link_to "Keybase", "https://keybase.io/download" %> to prove a cryptographic connection.
|
||||||
|
<% end %>
|
|
@ -0,0 +1,24 @@
|
||||||
|
# see https://keybase.io/docs/proof_integration_guide#1-config
|
||||||
|
|
||||||
|
JSON.pretty_generate({
|
||||||
|
"version": 1,
|
||||||
|
"domain": @domain,
|
||||||
|
"display_name": @name,
|
||||||
|
"description": @description,
|
||||||
|
"brand_color": @brand_color,
|
||||||
|
"logo": {
|
||||||
|
"svg_black": @logo_black,
|
||||||
|
"svg_full": @logo_full
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"re": @user_re,
|
||||||
|
"min": 1,
|
||||||
|
"max": 25
|
||||||
|
},
|
||||||
|
"prefill_url": @prefill_url,
|
||||||
|
"profile_url": @profile_url,
|
||||||
|
"check_url": @check_url,
|
||||||
|
"check_path": ["keybase_signatures"],
|
||||||
|
"avatar_path": ["avatar_url"],
|
||||||
|
"contact": @contacts
|
||||||
|
})
|
|
@ -0,0 +1,25 @@
|
||||||
|
<div class="box wide">
|
||||||
|
<div class="legend">
|
||||||
|
<span>
|
||||||
|
Connect Your Account (<%= @user.username %>) to Keybase
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="gravatar">
|
||||||
|
<%= avatar_img(@user, 100) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= image_tag @kb_avatar, width: 100 %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%= form_with(url: keybase_proofs_url, scope: :keybase_proof) do |f| %>
|
||||||
|
Is this you on Keybase? <b><i><%= @kb_username %></b></i>
|
||||||
|
<%= f.hidden_field "kb_username", value: @kb_username %>
|
||||||
|
<%= f.hidden_field "kb_signature", value: @kb_signature %>
|
||||||
|
<%= f.hidden_field "kb_ua", value: @kb_ua %>
|
||||||
|
<%= f.hidden_field "username", value: @user.username %>
|
||||||
|
<p>
|
||||||
|
<%= f.submit "Confirm" %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
|
@ -144,6 +144,17 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if Keybase.enabled? %>
|
||||||
|
<div class="boxline">
|
||||||
|
<%= f.label :kb_username,
|
||||||
|
raw("<a href=\"https://keybase.io/\">Keybase</a>:"),
|
||||||
|
:class => "required" %>
|
||||||
|
<span>
|
||||||
|
<%= render :partial => "keybase_proofs/proofs", locals: {user: @edit_user, for_self: true} %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
|
|
|
@ -149,9 +149,20 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="na">A mystery...</span>
|
<span class="na">A mystery...</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<br>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if Keybase.enabled? %>
|
||||||
|
<% for_self = (@showing_user == @user) %>
|
||||||
|
<% if @showing_user.keybase_signatures? || for_self %>
|
||||||
|
<label class="required">Keybase:</label>
|
||||||
|
<span class="d">
|
||||||
|
<%= render :partial => "keybase_proofs/proofs", locals: {user: @showing_user, for_self: for_self} %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if @user && @user.is_moderator? && !@showing_user.is_moderator? %>
|
<% if @user && @user.is_moderator? && !@showing_user.is_moderator? %>
|
||||||
<h2>Moderator Information</h2>
|
<h2>Moderator Information</h2>
|
||||||
|
|
||||||
|
|
|
@ -35,5 +35,8 @@ if Rails.env.production?
|
||||||
|
|
||||||
BCrypt::Engine.cost = 12
|
BCrypt::Engine.cost = 12
|
||||||
|
|
||||||
|
Keybase.DOMAIN = Rails.application.domain
|
||||||
|
Keybase.BASE_URL = ENV.fetch('KEYBASE_BASE_URL') { 'https://keybase.io' }
|
||||||
|
|
||||||
ActionMailer::Base.delivery_method = :sendmail
|
ActionMailer::Base.delivery_method = :sendmail
|
||||||
end
|
end
|
||||||
|
|
|
@ -147,6 +147,9 @@ Rails.application.routes.draw do
|
||||||
get "/settings/twitter_callback" => "settings#twitter_callback"
|
get "/settings/twitter_callback" => "settings#twitter_callback"
|
||||||
post "/settings/twitter_disconnect" => "settings#twitter_disconnect"
|
post "/settings/twitter_disconnect" => "settings#twitter_disconnect"
|
||||||
|
|
||||||
|
resources :keybase_proofs, only: [:new, :create]
|
||||||
|
get "/.well-known/keybase-proof-config" => "keybase_proofs#kbconfig", :as => "keybase_config"
|
||||||
|
|
||||||
get "/filters" => "filters#index"
|
get "/filters" => "filters#index"
|
||||||
post "/filters" => "filters#update"
|
post "/filters" => "filters#update"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
class Keybase
|
||||||
|
cattr_accessor :DOMAIN
|
||||||
|
cattr_accessor :BASE_URL
|
||||||
|
|
||||||
|
# these need to be overridden in config/initializers/production.rb
|
||||||
|
@@DOMAIN = nil
|
||||||
|
@@BASE_URL = nil
|
||||||
|
|
||||||
|
def self.enabled?
|
||||||
|
@@DOMAIN.present? || ENV['KEYBASE_BASE_URL']
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.avatar_url(kb_username)
|
||||||
|
s = Sponge.new
|
||||||
|
url = [
|
||||||
|
File.join(base_url, '/_/api/1.0/user/pic_url.json?'),
|
||||||
|
"username=#{kb_username}",
|
||||||
|
].join('')
|
||||||
|
res = s.fetch(url, :get).body
|
||||||
|
return JSON.parse(res).fetch('pic_url', default_keybase_avatar_url)
|
||||||
|
rescue ::DNSError, ::JSON::ParserError
|
||||||
|
default_keybase_avatar_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.proof_valid?(kb_username, kb_signature, username)
|
||||||
|
s = Sponge.new
|
||||||
|
url = [
|
||||||
|
File.join(base_url, '/_/api/1.0/sig/proof_valid.json?'),
|
||||||
|
"domain=#{@@DOMAIN}&",
|
||||||
|
"kb_username=#{kb_username}&",
|
||||||
|
"sig_hash=#{kb_signature}&",
|
||||||
|
"username=#{username}",
|
||||||
|
].join('')
|
||||||
|
res = s.fetch(url, :get).body
|
||||||
|
js = JSON.parse(res)
|
||||||
|
return js && js["proof_valid"].present? && js["proof_valid"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.success_url(kb_username, kb_signature, kb_ua, username)
|
||||||
|
File.join(base_url, "/_/proof_creation_success?domain=#{@@DOMAIN}&" \
|
||||||
|
"kb_username=#{kb_username}&username=#{username}&" \
|
||||||
|
"sig_hash=#{kb_signature}&kb_ua=#{kb_ua}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.default_keybase_avatar_url
|
||||||
|
"https://keybase.io/images/icons/icon-keybase-logo-48@2x.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.base_url
|
||||||
|
@@BASE_URL || "https://keybase.io"
|
||||||
|
end
|
||||||
|
end
|
|
@ -141,6 +141,8 @@ class Sponge
|
||||||
end
|
end
|
||||||
|
|
||||||
if BAD_NETS.select {|n| IPAddr.new(n).include?(ip) }.any?
|
if BAD_NETS.select {|n| IPAddr.new(n).include?(ip) }.any?
|
||||||
|
# This blocks all requests to localhost, so you might need to comment
|
||||||
|
# it out if you're building an end-to-end integration locally.
|
||||||
raise BadIPsError.new("refusing to talk to IP #{ip}")
|
raise BadIPsError.new("refusing to talk to IP #{ip}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe KeybaseProofsController do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:kb_username) { "cryptojim" }
|
||||||
|
let(:kb_sig) { "1"*66 }
|
||||||
|
let(:valid_kb_params) do
|
||||||
|
{ kb_username: kb_username, kb_signature: kb_sig,
|
||||||
|
kb_ua: "sega-genesis", username: user.username, }
|
||||||
|
end
|
||||||
|
let(:new_params) { valid_kb_params }
|
||||||
|
let(:create_params) { { keybase_proof: valid_kb_params } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_login_as user
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'new' do
|
||||||
|
it 'renders the expected kb_username' do
|
||||||
|
get :new, params: new_params
|
||||||
|
expect(response.body).to include(kb_username)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'create' do
|
||||||
|
context 'when the user does not already have a proof' do
|
||||||
|
it 'saves the signature to the user settings' do
|
||||||
|
expect(Keybase).to receive(:proof_valid?)
|
||||||
|
.with(kb_username, kb_sig, user.username).and_return(true)
|
||||||
|
|
||||||
|
post :create, params: create_params
|
||||||
|
|
||||||
|
expect(user.reload.keybase_signatures).to eq [
|
||||||
|
{ 'kb_username' => kb_username, 'sig_hash' => kb_sig },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user already has proofs' do
|
||||||
|
let(:other_kb_username) { 'somethingelse' }
|
||||||
|
let(:other_kb_sig) { '3'*66 }
|
||||||
|
let(:expected_keybase_signatures) do
|
||||||
|
[
|
||||||
|
{ 'kb_username' => kb_username, 'sig_hash' => kb_sig },
|
||||||
|
{ 'kb_username' => other_kb_username, 'sig_hash' => other_kb_sig },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.add_or_update_keybase_proof(kb_username, '2'*66)
|
||||||
|
user.add_or_update_keybase_proof(other_kb_username, other_kb_sig)
|
||||||
|
user.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the signature for the matching user and retains any others' do
|
||||||
|
expect(Keybase).to receive(:proof_valid?)
|
||||||
|
.with(kb_username, kb_sig, user.username).and_return(true)
|
||||||
|
|
||||||
|
post :create, params: create_params
|
||||||
|
|
||||||
|
expect(user.reload.keybase_signatures).to match_array expected_keybase_signatures
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue