blockstack 7.0.0 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fdf4936b41292c2309f116a60b9a86dbd4909ea3
4
- data.tar.gz: a414239cee794d16ccedb5a2c39c65ba370a7d52
3
+ metadata.gz: 98dc45c77ec49a8d6725daf6e4eb4e642edad166
4
+ data.tar.gz: 02e764103cafbc18bac059b23487a8827ccc1128
5
5
  SHA512:
6
- metadata.gz: 7be8209da468a56ed390d2c5175a6f94c448eb3cab903075be1f881fdcf6daf1b36d781b4d991c097b6f64e562a58266f5c10e47924604d229d887bf8d402599
7
- data.tar.gz: f1d7e39d5284ec7ae5df63bab0f68e101968926fa56ff6decfa8dfb078d071f1b129dc5e3e25d9c3d72e45bb64217b3a47db8d881494180f253d4db63a6c5701
6
+ metadata.gz: 64266c0200959358ae3f0e06d3d7678d56bc1a0c9030e831dd21b56b209e0d2397a636cdf47d04107b6b960b11e18d9124172504aa3f62f1c138f886b4d81d43
7
+ data.tar.gz: 589cec9d95fc24c24f64679da6b3f4fdea85e9d9924044fd0a1038f8482fd80e5aa074771a5f81f4d7b6c4746e9490a480bda99b1d1a73753efb2876224d6b8a
data/README.md CHANGED
@@ -34,7 +34,7 @@ the [OmniAuth Blockstack strategy](https://github.com/blockstack/omniauth-blocks
34
34
  ### To verify an auth response
35
35
 
36
36
  ```ruby
37
- require "blockstack"
37
+ require 'blockstack'
38
38
 
39
39
  begin
40
40
  auth_response = request.params['authResponse']
data/Rakefile CHANGED
@@ -1,7 +1,13 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new(:rubocop) do |t|
6
+ t.options = ['--lint']
7
+ end
3
8
 
4
9
  RSpec::Core::RakeTask.new
5
10
 
6
- task :default => :spec
7
- task :test => :spec
11
+
12
+ task test: [:rubocop, :spec]
13
+ task default: :test
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "blockstack"
3
+ require 'bundler/setup'
4
+ require 'blockstack'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
9
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
10
+ # require 'pry'
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start
data/blockstack.gemspec CHANGED
@@ -4,29 +4,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'blockstack/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "blockstack"
7
+ spec.name = 'blockstack'
8
8
  spec.version = Blockstack::VERSION
9
- spec.authors = ["Larry Salibra"]
10
- spec.email = ["rubygems@larrysalibra.com"]
11
- spec.summary = "The Blockstack ruby library for identity and authentication"
12
- spec.homepage = "https://github.com/larrysalibra/blockstack-ruby"
13
- spec.license = "MIT"
9
+ spec.authors = ['Larry Salibra']
10
+ spec.email = ['rubygems@larrysalibra.com']
11
+ spec.summary = 'The Blockstack ruby library for identity and authentication'
12
+ spec.homepage = 'https://github.com/larrysalibra/blockstack-ruby'
13
+ spec.license = 'MIT'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
- f.match(%r{^(test|spec|features)/})
16
+ f.match(%r{^(test|spec|features)/})
17
17
  end
18
- spec.bindir = "exe"
18
+ spec.bindir = 'exe'
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency "bundler", "~> 1.13"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "rspec"
25
- spec.add_development_dependency "webmock"
26
- spec.add_development_dependency "vcr"
27
-
28
- spec.add_dependency "jwtb", "2.0.0.beta2.bsk1"
29
- spec.add_dependency "bitcoin-ruby"
30
- spec.add_dependency "faraday"
22
+ spec.add_development_dependency 'bundler', '~> 1.13'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'webmock'
26
+ spec.add_development_dependency 'vcr'
27
+ spec.add_development_dependency 'rubocop'
31
28
 
29
+ spec.add_dependency 'jwtb', '2.0.0.beta2.bsk1'
30
+ spec.add_dependency 'bitcoin-ruby'
31
+ spec.add_dependency 'faraday'
32
32
  end
@@ -1,124 +1,119 @@
1
1
  module Blockstack
2
2
  class User
3
- def self.from_json(json, username)
4
- User.new(json, username)
5
- end
3
+ def self.from_json(json, username)
4
+ User.new(json, username)
5
+ end
6
6
 
7
- attr_reader :username
8
- attr_reader :name_formatted
9
- attr_reader :avatar_url
10
- attr_reader :cover_url
11
- attr_reader :location_formatted
12
- attr_reader :website
13
- attr_reader :bio
14
- attr_reader :angellist_username
15
- attr_reader :github_username
16
- attr_reader :facebook_username
17
- attr_reader :twitter_username
18
- attr_reader :instagram_username
19
- attr_reader :linkedin_url
20
- attr_reader :bitcoin_address
21
- attr_reader :bitmessage_address
22
- attr_reader :bitcoinotc_username
23
- attr_reader :pgp_fingerprint
24
- attr_reader :pgp_url
25
- attr_reader :orgs
26
- attr_reader :schema_version
7
+ attr_reader :username
8
+ attr_reader :name_formatted
9
+ attr_reader :avatar_url
10
+ attr_reader :cover_url
11
+ attr_reader :location_formatted
12
+ attr_reader :website
13
+ attr_reader :bio
14
+ attr_reader :angellist_username
15
+ attr_reader :github_username
16
+ attr_reader :facebook_username
17
+ attr_reader :twitter_username
18
+ attr_reader :instagram_username
19
+ attr_reader :linkedin_url
20
+ attr_reader :bitcoin_address
21
+ attr_reader :bitmessage_address
22
+ attr_reader :bitcoinotc_username
23
+ attr_reader :pgp_fingerprint
24
+ attr_reader :pgp_url
25
+ attr_reader :orgs
26
+ attr_reader :schema_version
27
27
 
28
- def initialize(json, username)
29
- if(json["profile"])
30
- json = json["profile"]
31
- end
32
- if(json['v'] == "0.2")
33
- @username = username
34
- @name_formatted = json["name"]["formatted"] if json["name"]
35
- @avatar_url = json["avatar"]["url"] if json["avatar"]
36
- @cover_url = json["cover"]["url"] if json["cover"]
37
- @location_formatted = json["location"]["formatted"] if json["location"]
38
- @website = json["website"]
39
- @bio = json["bio"]
40
- @angellist_username = json["angellist"]["username"] if json["angellist"]
41
- @github_username = json["github"]["username"] if json["github"]
42
- @facebook_username = json["facebook"]["username"] if json["facebook"]
43
- @twitter_username = json["twitter"]["username"] if json["twitter"]
44
- @instagram_username = json["instagram"]["username"] if json["instagram"]
45
- @linkedin_url = json["linkedin"]["url"] if json["linkedin"]
46
- @bitcoin_address = json["bitcoin"]["address"] if json["bitcoin"]
47
- @bitmessage_address = json["bitmessage"]["address"] if json["bitmessage"]
48
- @bitcoinotc_username = json["bitcoinotc"]["username"] if json["bitcoinotc"]
49
- @pgp_fingerprint = json["pgp"]["fingerprint"] if json["pgp"]
50
- @pgp_url = json["pgp"]["url"] if json["pgp"]
51
- @schema_version = json["v"]
52
- @orgs = parse_orgs(json["orgs"])
53
- else
54
- @username = username
55
- @name_formatted = json["name"] if json["name"]
56
- @avatar_url = find_image_url(json, 'avatar')
57
- @cover_url = find_image_url(json, 'cover')
58
- @location_formatted = json["address"]["addressLocality"] if json["address"]
59
- @website = json["website"][0]["url"] if json["website"] && json["website"][0]
60
- @bio = json["description"]
61
- @angellist_username = find_account_username(json, "angellist")
62
- @github_username = find_account_username(json, "github")
63
- @facebook_username = find_account_username(json, "facebook")
64
- @twitter_username = find_account_username(json, "twitter")
65
- @instagram_username = find_account_username(json, "instagram")
66
- @linkedin_url = find_account_username(json, "linkedin")
67
- @bitcoin_address = find_account_username(json, "bitcoin")
68
- @bitmessage_address = find_account_username(json, "bitmessage")
69
- @bitcoinotc_username = find_account_username(json, "bitcoinotc")
70
- @pgp_fingerprint = find_account_username(json, "pgp")
71
- @pgp_url = find_account(json, "pgp")['contentUrl'] if @pgp_fingerprint
72
- @schema_version = "0.3"
73
- @orgs = parse_orgs(json["orgs"])
74
- end
28
+ def initialize(json, username)
29
+ json = json['profile'] if json['profile']
75
30
 
31
+ if json['v'] == '0.2'
32
+ @username = username
33
+ @name_formatted = json['name']['formatted'] if json['name']
34
+ @avatar_url = json['avatar']['url'] if json['avatar']
35
+ @cover_url = json['cover']['url'] if json['cover']
36
+ @location_formatted = json['location']['formatted'] if json['location']
37
+ @website = json['website']
38
+ @bio = json['bio']
39
+ @angellist_username = json['angellist']['username'] if json['angellist']
40
+ @github_username = json['github']['username'] if json['github']
41
+ @facebook_username = json['facebook']['username'] if json['facebook']
42
+ @twitter_username = json['twitter']['username'] if json['twitter']
43
+ @instagram_username = json['instagram']['username'] if json['instagram']
44
+ @linkedin_url = json['linkedin']['url'] if json['linkedin']
45
+ @bitcoin_address = json['bitcoin']['address'] if json['bitcoin']
46
+ @bitmessage_address = json['bitmessage']['address'] if json['bitmessage']
47
+ @bitcoinotc_username = json['bitcoinotc']['username'] if json['bitcoinotc']
48
+ @pgp_fingerprint = json['pgp']['fingerprint'] if json['pgp']
49
+ @pgp_url = json['pgp']['url'] if json['pgp']
50
+ @schema_version = json['v']
51
+ @orgs = parse_orgs(json['orgs'])
52
+ else
53
+ @username = username
54
+ @name_formatted = json['name'] if json['name']
55
+ @avatar_url = find_image_url(json, 'avatar')
56
+ @cover_url = find_image_url(json, 'cover')
57
+ @location_formatted = json['address']['addressLocality'] if json['address']
58
+ @website = json['website'][0]['url'] if json['website'] && json['website'][0]
59
+ @bio = json['description']
60
+ @angellist_username = find_account_username(json, 'angellist')
61
+ @github_username = find_account_username(json, 'github')
62
+ @facebook_username = find_account_username(json, 'facebook')
63
+ @twitter_username = find_account_username(json, 'twitter')
64
+ @instagram_username = find_account_username(json, 'instagram')
65
+ @linkedin_url = find_account_username(json, 'linkedin')
66
+ @bitcoin_address = find_account_username(json, 'bitcoin')
67
+ @bitmessage_address = find_account_username(json, 'bitmessage')
68
+ @bitcoinotc_username = find_account_username(json, 'bitcoinotc')
69
+ @pgp_fingerprint = find_account_username(json, 'pgp')
70
+ @pgp_url = find_account(json, 'pgp')['contentUrl'] if @pgp_fingerprint
71
+ @schema_version = '0.3'
72
+ @orgs = parse_orgs(json['orgs'])
76
73
  end
74
+ end
77
75
 
78
- def openname
79
- warn "[DEPRECATION] `openname` is deprecated. Please use `username` instead."
80
- username
81
- end
76
+ def openname
77
+ warn '[DEPRECATION] `openname` is deprecated. Please use `username` instead.'
78
+ username
79
+ end
82
80
 
83
- protected
81
+ protected
84
82
 
85
- def find_image_url(json, type)
86
- images = json['image']
87
- if(images && images.kind_of?(Array))
88
- for image in images
89
- return image['contentUrl'] if image['name'] == type
90
- end
83
+ def find_image_url(json, type)
84
+ images = json['image']
85
+ if images && images.is_a?(Array)
86
+ images.each do |image|
87
+ return image['contentUrl'] if image['name'] == type
91
88
  end
92
- nil
93
89
  end
90
+ nil
91
+ end
94
92
 
95
- def find_account(json, service)
96
- accounts = json['account']
97
- if(accounts && accounts.kind_of?(Array))
98
- for account in accounts
99
- return account if account['service'] == service
100
- end
93
+ def find_account(json, service)
94
+ accounts = json['account']
95
+ if accounts && accounts.is_a?(Array)
96
+ accounts.each do |account|
97
+ return account if account['service'] == service
101
98
  end
102
- nil
103
99
  end
100
+ nil
101
+ end
104
102
 
105
- def find_account_username(json, service)
106
- account = find_account(json, service)
107
- if(account)
108
- return account['identifier']
109
- end
110
- nil
111
- end
103
+ def find_account_username(json, service)
104
+ account = find_account(json, service)
105
+ return account['identifier'] if account
106
+ nil
107
+ end
112
108
 
113
- def parse_orgs(orgs_json)
114
- orgs = Array.new
115
- if orgs_json
116
- for org_json in orgs_json
117
- orgs << Org.new(org_json)
118
- end
119
- end
120
- orgs
109
+ def parse_orgs(orgs_json)
110
+ orgs = []
111
+ if orgs_json
112
+ orgs_json.each do |org_json|
113
+ orgs << Org.new(org_json)
114
+ end
121
115
  end
122
-
116
+ orgs
117
+ end
123
118
  end
124
119
  end
@@ -1,3 +1,3 @@
1
1
  module Blockstack
2
- VERSION = "7.0.0"
2
+ VERSION = "7.0.1"
3
3
  end
data/lib/blockstack.rb CHANGED
@@ -1,137 +1,137 @@
1
- require "blockstack/version"
2
- require "blockstack/user"
3
- require "bitcoin"
4
- require "faraday"
5
- require "jwtb"
1
+ require 'blockstack/version'
2
+ require 'blockstack/user'
3
+ require 'bitcoin'
4
+ require 'faraday'
5
+ require 'jwtb'
6
6
 
7
7
  module Blockstack
8
- class InvalidAuthResponse < StandardError; end
9
-
10
- USER_AGENT = "blockstack-ruby #{VERSION}"
11
- ALGORITHM = "ES256K"
12
- REQUIRED_CLAIMS = %w(iss iat jti exp username profile public_keys)
13
-
14
- DEFAULT_LEEWAY = 30 # seconds
15
- DEFAULT_VALID_WITHIN = 30 # seconds
16
- DEFAULT_API = "https://core.blockstack.org"
17
-
18
- def self.api=(api)
19
- @@api = api.nil? ? DEFAULT_API : api
20
- end
21
-
22
- def self.api
23
- @@api
24
- end
25
-
26
- def self.leeway=(leeway)
27
- @@leeway = leeway.nil? ? DEFAULT_LEEWAY : leeway
28
- end
29
-
30
- def self.leeway
31
- @@leeway
32
- end
33
-
34
- def self.valid_within=(valid_within)
35
- @@valid_within = valid_within.nil? ? DEFAULT_VALID_WITHIN : valid_within
36
- end
37
-
38
- def self.valid_within
39
- @@valid_within
40
- end
41
-
42
- def self.verify_auth_response(auth_token)
43
- # decode & verify token without checking signature so we can extract
44
- # public keys
45
- public_key = nil
46
- verify = false
47
- decoded_tokens = JWTB.decode auth_token, public_key, verify, algorithm: ALGORITHM
48
- decoded_token = decoded_tokens[0]
49
-
50
- REQUIRED_CLAIMS.each do |field|
51
- raise InvalidAuthResponse.new("Missing required '#{field}' claim.") if !decoded_token.key?(field.to_s)
52
- end
53
- raise InvalidAuthResponse.new("Missing required 'iat' claim.") if !decoded_token["iat"]
54
- raise InvalidAuthResponse.new("'iat' timestamp claim is skewed too far from present.") if (Time.now.to_i - decoded_token["iat"]).abs > self.valid_within
55
-
56
- public_keys = decoded_token['public_keys']
57
-
58
- raise InvalidAuthResponse.new("Invalid public_keys array: only 1 key is supported") unless public_keys.length == 1
59
-
60
- compressed_hex_public_key = public_keys[0]
61
- bignum = OpenSSL::BN.new(compressed_hex_public_key, 16)
62
- group = OpenSSL::PKey::EC::Group.new 'secp256k1'
63
- public_key = OpenSSL::PKey::EC::Point.new(group, bignum)
64
- ecdsa_key = OpenSSL::PKey::EC.new 'secp256k1'
65
- ecdsa_key.public_key = public_key
66
- verify = true
67
-
68
- # decode & verify signature
69
- decoded_tokens = JWTB.decode auth_token, ecdsa_key, verify, algorithm: ALGORITHM, exp_leeway: self.leeway
70
- decoded_token = decoded_tokens[0]
71
-
72
- raise InvalidAuthResponse.new("Public keys don't match issuer address") unless self.public_keys_match_issuer?(decoded_token)
73
-
74
- raise InvalidAuthResponse.new("Public keys don't match owner of claimed username") unless self.public_keys_match_username?(decoded_token)
75
-
76
- return decoded_token
77
- rescue JWTB::VerificationError => error
78
- raise InvalidAuthResponse.new("Signature on JWT is invalid")
79
- rescue JWTB::DecodeError => error
80
- raise InvalidAuthResponse.new("Unable to decode JWT")
81
- rescue RuntimeError => error
82
- raise InvalidAuthResponse.new(error.message)
83
- end
84
-
85
- def self.get_did_type(decentralized_id)
86
- did_parts = decentralized_id.split(':')
87
- raise 'Decentralized IDs must have 3 parts' if did_parts.length != 3
88
- raise 'Decentralized IDs must start with "did"' if did_parts[0].downcase != 'did'
89
- did_parts[1].downcase
8
+ class InvalidAuthResponse < StandardError; end
9
+
10
+ USER_AGENT = "blockstack-ruby #{VERSION}"
11
+ ALGORITHM = 'ES256K'
12
+ REQUIRED_CLAIMS = %w(iss iat jti exp username profile public_keys)
13
+
14
+ DEFAULT_LEEWAY = 30 # seconds
15
+ DEFAULT_VALID_WITHIN = 30 # seconds
16
+ DEFAULT_API = 'https://core.blockstack.org'
17
+
18
+ def self.api=(api)
19
+ @api = api || DEFAULT_API
20
+ end
21
+
22
+ def self.api
23
+ @api
24
+ end
25
+
26
+ def self.leeway=(leeway)
27
+ @leeway = leeway || DEFAULT_LEEWAY
28
+ end
29
+
30
+ def self.leeway
31
+ @leeway
32
+ end
33
+
34
+ def self.valid_within=(valid_within)
35
+ @valid_within = valid_within || DEFAULT_VALID_WITHIN
36
+ end
37
+
38
+ def self.valid_within
39
+ @valid_within
40
+ end
41
+
42
+ # decode & verify token without checking signature so we can extract
43
+ # public keys
44
+ def self.verify_without_signature(auth_token)
45
+ public_key = nil
46
+ verify = false
47
+ decoded_tokens = JWTB.decode auth_token, public_key, verify, algorithm: ALGORITHM
48
+ decoded_tokens[0]
49
+ end
50
+
51
+ # decode & verify signature
52
+ def self.verify_with_signature(auth_token)
53
+ compressed_hex_public_key = public_keys[0]
54
+ bignum = OpenSSL::BN.new(compressed_hex_public_key, 16)
55
+ group = OpenSSL::PKey::EC::Group.new 'secp256k1'
56
+ public_key = OpenSSL::PKey::EC::Point.new(group, bignum)
57
+ ecdsa_key = OpenSSL::PKey::EC.new 'secp256k1'
58
+ ecdsa_key.public_key = public_key
59
+ verify = true
60
+
61
+ decoded_tokens = JWTB.decode auth_token, ecdsa_key, verify, algorithm: ALGORITHM, exp_leeway: leeway
62
+ decoded_tokens[0]
63
+ end
64
+
65
+ def self.verify_auth_response(auth_token)
66
+ decoded_token = verify_without_signature(auth_token)
67
+
68
+ REQUIRED_CLAIMS.each do |field|
69
+ fail InvalidAuthResponse.new("Missing required '#{field}' claim.") unless decoded_token.key?(field.to_s)
90
70
  end
91
-
92
- def self.get_address_from_did(decentralized_id)
93
- did_type = get_did_type(decentralized_id)
94
- return nil if did_type != 'btc-addr'
95
- decentralized_id.split(':')[2]
96
- end
97
-
98
- protected
99
-
100
- def self.public_keys_match_issuer?(decoded_token)
101
- public_keys = decoded_token['public_keys']
102
- address_from_issuer = get_address_from_did(decoded_token['iss'])
103
-
104
- raise 'Multiple public keys are not supported' unless public_keys.count == 1
105
-
106
- address_from_public_keys = Bitcoin::pubkey_to_address public_keys.first
107
-
108
- address_from_issuer == address_from_public_keys
109
- end
110
-
111
- def self.public_keys_match_username?(decoded_token)
112
- username = decoded_token["username"]
113
- return true if username.nil?
114
-
115
- response = Faraday.get "#{self.api}/v1/names/#{username}"
116
- json = JSON.parse response.body
117
-
118
- raise "Issuer claimed username that doesn't exist" if response.status == 404
119
- raise "Unable to verify issuer's claimed username" if response.status != 200
120
-
121
- name_owning_address = json['address']
122
- address_from_issuer = get_address_from_did decoded_token['iss']
123
- name_owning_address == address_from_issuer
124
- end
125
-
126
- def self.faraday
127
- connection = Faraday.new
128
- connection.headers[:user_agent] = USER_AGENT
129
- connection
130
- end
131
-
132
- private
133
-
134
- @@leeway = DEFAULT_LEEWAY
135
- @@valid_within = DEFAULT_VALID_WITHIN
136
- @@api = DEFAULT_API
71
+ fail InvalidAuthResponse.new("Missing required 'iat' claim.") unless decoded_token['iat']
72
+ fail InvalidAuthResponse.new("'iat' timestamp claim is skewed too far from present.") if (Time.now.to_i - decoded_token['iat']).abs > valid_within
73
+
74
+ public_keys = decoded_token['public_keys']
75
+ fail InvalidAuthResponse.new('Invalid public_keys array: only 1 key is supported') unless public_keys.length == 1
76
+
77
+ decoded_token = verify_with_signature(auth_token)
78
+ fail InvalidAuthResponse.new("Public keys don't match issuer address") unless self.public_keys_match_issuer?(decoded_token)
79
+ fail InvalidAuthResponse.new("Public keys don't match owner of claimed username") unless self.public_keys_match_username?(decoded_token)
80
+
81
+ return decoded_token
82
+ rescue JWTB::VerificationError
83
+ raise InvalidAuthResponse.new('Signature on JWT is invalid')
84
+ rescue JWTB::DecodeError
85
+ raise InvalidAuthResponse.new('Unable to decode JWT')
86
+ rescue RuntimeError => error
87
+ raise InvalidAuthResponse.new(error.message)
88
+ end
89
+
90
+ def self.get_did_type(decentralized_id)
91
+ did_parts = decentralized_id.split(':')
92
+ fail 'Decentralized IDs must have 3 parts' if did_parts.length != 3
93
+ fail 'Decentralized IDs must start with "did"' if did_parts[0].downcase != 'did'
94
+ did_parts[1].downcase
95
+ end
96
+
97
+ def self.get_address_from_did(decentralized_id)
98
+ did_type = get_did_type(decentralized_id)
99
+ return nil if did_type != 'btc-addr'
100
+ decentralized_id.split(':')[2]
101
+ end
102
+
103
+ def self.public_keys_match_issuer?(decoded_token)
104
+ public_keys = decoded_token['public_keys']
105
+ address_from_issuer = get_address_from_did(decoded_token['iss'])
106
+
107
+ fail 'Multiple public keys are not supported' unless public_keys.count == 1
108
+
109
+ address_from_public_keys = Bitcoin.pubkey_to_address(public_keys.first)
110
+ address_from_issuer == address_from_public_keys
111
+ end
112
+
113
+ def self.public_keys_match_username?(decoded_token)
114
+ username = decoded_token['username']
115
+ return true if username.nil?
116
+
117
+ response = Faraday.get "#{api}/v1/names/#{username}"
118
+ json = JSON.parse response.body
119
+
120
+ fail "Issuer claimed username that doesn't exist" if response.status == 404
121
+ fail "Unable to verify issuer's claimed username" if response.status != 200
122
+
123
+ name_owning_address = json['address']
124
+ address_from_issuer = get_address_from_did decoded_token['iss']
125
+ name_owning_address == address_from_issuer
126
+ end
127
+
128
+ def self.faraday
129
+ connection = Faraday.new
130
+ connection.headers[:user_agent] = USER_AGENT
131
+ connection
132
+ end
133
+
134
+ @leeway = DEFAULT_LEEWAY
135
+ @valid_within = DEFAULT_VALID_WITHIN
136
+ @api = DEFAULT_API
137
137
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blockstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Larry Salibra
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
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'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: jwtb
85
99
  requirement: !ruby/object:Gem::Requirement