blockstack 7.0.0 → 7.0.1

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 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