siwe 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5c8a137e642d2e6fe2fcb52a9d79dea2580eba1cc564889cf08baee669487c3
4
- data.tar.gz: b55b6f1645681d007f678d692611ee40600a4406ab37f0b45182d92c374bb6f7
3
+ metadata.gz: 933ba5a3c973570550d45cd839212a57c6a66568e17a2b107be945fc5ee1c6f3
4
+ data.tar.gz: 6ea0957b133e576102f9038cee3a7c4459cba2d7bbbe11d4268ac8d464e5982b
5
5
  SHA512:
6
- metadata.gz: dcff7464e7f27da76e5d53209f234490e76be3f9360ce5fceed3bf62975cef6c88dba6876c1590b0821bde63e0514e04033001b555a29b1a208e01121aab0f3d
7
- data.tar.gz: 75055a13223cde52dfcc1ac75e1be8a466a80d1445f55aa724a9304be60c281c006f636302b588c40f31684fe8cf7bbe5f1fee1221dbe58280f03f4721e36e52
6
+ metadata.gz: 9ecf2bac358261ae3cd49f8c5ef43ddc7724360705dddaf2704cf3ba427bbb2181bc20a5189cc66bd991144cc669a8fb9555db36780f439dfa6b0d007c941202
7
+ data.tar.gz: d63ad437299d1386f5cfce1ce44a4d76ecd7a8bcffd3e819f3d2b625196a3b4ced5128544a1413c76447b918e901fa78b63ed4268b6785c631ba18e3675c5327
data/.rubocop.yml CHANGED
@@ -32,5 +32,11 @@ Metrics/ClassLength:
32
32
  Style/OptionalBooleanParameter:
33
33
  Enabled: false
34
34
 
35
+ Style/RegexpLiteral:
36
+ Enabled: false
37
+
38
+ Lint/MixedRegexpCaptureTypes:
39
+ Enabled: false
40
+
35
41
  Layout/LineLength:
36
- Max: 120
42
+ Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- siwe (1.1.1)
4
+ siwe (2.0.0)
5
5
  eth (~> 0.5.1)
6
6
 
7
7
  GEM
@@ -12,7 +12,7 @@ GEM
12
12
  benchmark (0.2.0)
13
13
  diff-lcs (1.5.0)
14
14
  e2mmap (0.1.0)
15
- eth (0.5.1)
15
+ eth (0.5.2)
16
16
  keccak (~> 1.3)
17
17
  konstructor (~> 1.0)
18
18
  openssl (~> 2.2)
@@ -3,7 +3,28 @@
3
3
  module Siwe
4
4
  # Used when the message is already expired. (Expires At < Time.now)
5
5
  class ExpiredMessage < StandardError
6
- def initialize(msg = "Message expired.")
6
+ def initialize(msg = "Expired message.")
7
+ super
8
+ end
9
+ end
10
+
11
+ # Used when the domain is not a valid authority or is empty.
12
+ class InvalidDomain < StandardError
13
+ def initialize(msg = "Invalid domain.")
14
+ super
15
+ end
16
+ end
17
+
18
+ # Used when the domain doesn't match the domain provided for verification.
19
+ class DomainMismatch < StandardError
20
+ def initialize(msg = "Domain does not match provided domain for verification.")
21
+ super
22
+ end
23
+ end
24
+
25
+ # Used when the nonce doesn't match the nonce provided for verification.
26
+ class NonceMismatch < StandardError
27
+ def initialize(msg = "Nonce does not match provided nonce for verification.")
7
28
  super
8
29
  end
9
30
  end
@@ -15,6 +36,20 @@ module Siwe
15
36
  end
16
37
  end
17
38
 
39
+ # Used when the message is created with an invalid URI
40
+ class InvalidURI < StandardError
41
+ def initialize(msg = "URI does not conform to RFC 3986.")
42
+ super
43
+ end
44
+ end
45
+
46
+ # Used when the nonce is smaller then 8 characters or is not alphanumeric
47
+ class InvalidNonce < StandardError
48
+ def initialize(msg = "Nonce size smaller then 8 characters or is not alphanumeric.")
49
+ super
50
+ end
51
+ end
52
+
18
53
  # Used when the message is not yet valid. (Not Before > Time.now)
19
54
  class NotValidMessage < StandardError
20
55
  def initialize(msg = "Message not yet valid.")
@@ -22,10 +57,31 @@ module Siwe
22
57
  end
23
58
  end
24
59
 
60
+ # Used when the message contains a time format not compliant to ISO8601.
61
+ class InvalidTimeFormat < StandardError
62
+ def initialize(field, msg = "Invalid time format for: #{field}")
63
+ super
64
+ end
65
+ end
66
+
67
+ # Used when the message version is not 1.
68
+ class InvalidMessageVersion < StandardError
69
+ def initialize(msg = "Invalid message version.")
70
+ super
71
+ end
72
+ end
73
+
25
74
  # Used when the signature doesn't correspond to the address of the message.
26
75
  class InvalidSignature < StandardError
27
76
  def initialize(msg = "Signature doesn't match message.")
28
77
  super
29
78
  end
30
79
  end
80
+
81
+ # Used when the message doesn't match the RegExp.
82
+ class UnableToParseMessage < StandardError
83
+ def initialize(msg = "Unable to parse message.")
84
+ super
85
+ end
86
+ end
31
87
  end
data/lib/siwe/message.rb CHANGED
@@ -4,26 +4,27 @@ require "time"
4
4
  require "eth"
5
5
  require "json"
6
6
 
7
- SIWE_DOMAIN = "^(?<domain>([^?#]*)) wants you to sign in with your Ethereum account:\\n"
8
- SIWE_ADDRESS = "(?<address>0x[a-zA-Z0-9]{40})\\n\\n"
9
- SIWE_STATEMENT = "((?<statement>[^\\n]+)\\n)?\\n"
10
- SIWE_URI = "(([^:?#]+):)?(([^?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))"
11
- SIWE_URI_LINE = "URI: (?<uri>#{SIWE_URI}?)\\n"
12
- SIWE_VERSION = "Version: (?<version>1)\\n"
13
- SIWE_CHAIN_ID = "Chain ID: (?<chain_id>[0-9]+)\\n"
14
- SIWE_NONCE = "Nonce: (?<nonce>[a-zA-Z0-9]{8,})\\n"
15
- SIWE_DATETIME = "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9])"\
16
- ":([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))"
17
- SIWE_ISSUED_AT = "Issued At: (?<issued_at>#{SIWE_DATETIME})"
18
- SIWE_EXPIRATION_TIME = "(\\nExpiration Time: (?<expiration_time>#{SIWE_DATETIME}))?"
19
- SIWE_NOT_BEFORE = "(\\nNot Before: (?<not_before>#{SIWE_DATETIME}))?"
20
- SIWE_REQUEST_ID = "(\\nRequest ID: (?<request_id>[-._~!$&'()*+,;=:@%a-zA-Z0-9]*))?"
21
- SIWE_RESOURCES = "(\\nResources:(?<resources>(\\n- #{SIWE_URI}?)+))?$"
22
-
23
- SIWE_MESSAGE = "#{SIWE_DOMAIN}#{SIWE_ADDRESS}#{SIWE_STATEMENT}#{SIWE_URI_LINE}#{SIWE_VERSION}#{SIWE_CHAIN_ID}"\
24
- "#{SIWE_NONCE}#{SIWE_ISSUED_AT}#{SIWE_EXPIRATION_TIME}#{SIWE_NOT_BEFORE}#{SIWE_REQUEST_ID}"\
25
- "#{SIWE_RESOURCES}"
26
-
7
+ DOMAIN = %r{(?<domain>[^/?#]+)}.freeze
8
+ SIWE_DOMAIN = %r{^#{DOMAIN.source} wants you to sign in with your Ethereum account:}.freeze
9
+
10
+ SIWE_ADDRESS = %r{\n(?<address>0x[a-zA-Z0-9]{40})\n\n}.freeze
11
+ SIWE_STATEMENT = %r{((?<statement>[^\n]+)\n)?}.freeze
12
+ RFC3986 = %r{(([^:?#]+):)?(([^?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}.freeze
13
+ SIWE_URI_LINE = %r{\nURI: (?<uri>#{RFC3986.source}?)}.freeze
14
+ SIWE_VERSION = %r{\nVersion: (?<version>1)}.freeze
15
+ SIWE_CHAIN_ID = %r{\nChain ID: (?<chain_id>[0-9]+)}.freeze
16
+ SIWE_NONCE = %r{\nNonce: (?<nonce>[a-zA-Z0-9]{8,})}.freeze
17
+ SIWE_DATETIME = %r{([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))}.freeze
18
+ SIWE_ISSUED_AT = %r{\nIssued At: (?<issued_at>#{SIWE_DATETIME.source})}.freeze
19
+ SIWE_EXPIRATION_TIME = %r{(\nExpiration Time: (?<expiration_time>#{SIWE_DATETIME.source}))?}.freeze
20
+ SIWE_NOT_BEFORE = %r{(\nNot Before: (?<not_before>#{SIWE_DATETIME.source}))?}.freeze
21
+ SIWE_REQUEST_ID = %r{(\nRequest ID: (?<request_id>[-._~!$&'()*+,;=:@%a-zA-Z0-9]*))?}.freeze
22
+ SIWE_RESOURCES = %r{(\nResources:(?<resources>(\n- #{RFC3986.source}?)+))?$}.freeze
23
+
24
+ SIWE_MESSAGE = Regexp.new(SIWE_DOMAIN.source + SIWE_ADDRESS.source + SIWE_STATEMENT.source + SIWE_URI_LINE.source +
25
+ SIWE_VERSION.source + SIWE_CHAIN_ID.source + SIWE_NONCE.source + SIWE_ISSUED_AT.source +
26
+ SIWE_EXPIRATION_TIME.source + SIWE_NOT_BEFORE.source + SIWE_REQUEST_ID.source +
27
+ SIWE_RESOURCES.source)
27
28
  module Siwe
28
29
  # Class that defines the EIP-4361 message fields and some utility methods to
29
30
  # generate/validate the messages
@@ -76,47 +77,41 @@ module Siwe
76
77
 
77
78
  def initialize(domain, address, uri, version, options = {})
78
79
  @domain = domain
79
- begin
80
- @address = Eth::Address.new(address).to_s
81
- rescue StandardError
82
- raise Siwe::InvalidAddress
83
- end
84
- raise Siwe::InvalidAddress unless @address.eql? address
85
-
80
+ @address = address
86
81
  @uri = uri
87
82
  @version = version
88
83
  @statement = options.fetch :statement, ""
89
84
  @issued_at = options.fetch :issued_at, Time.now.utc.iso8601
90
85
  @nonce = options.fetch :nonce, Siwe::Util.generate_nonce
91
- @chain_id = options.fetch :chain_id, "1"
86
+ @chain_id = options.fetch :chain_id, 1
92
87
  @expiration_time = options.fetch :expiration_time, ""
93
88
  @not_before = options.fetch :not_before, ""
94
89
  @request_id = options.fetch :request_id, ""
95
90
  @resources = options.fetch :resources, []
91
+ validate
96
92
  end
97
93
 
98
94
  def self.from_message(msg)
99
- if (message = msg.match SIWE_MESSAGE)
100
- new(
101
- message[:domain],
102
- Eth::Address.new(message[:address]).to_s,
103
- message[:uri],
104
- message[:version],
105
- {
106
- statement: message[:statement] || "",
107
- issued_at: message[:issued_at],
108
- nonce: message[:nonce],
109
- chain_id: message[:chain_id],
110
- expiration_time: message[:expiration_time] || "",
111
- not_before: message[:not_before] || "",
112
- request_id: message[:request_id] || "",
113
- resources: message[:resources]&.split("\n- ")&.drop(1) || []
114
- }
115
- )
116
-
117
- else
118
- throw "Invalid message input."
119
- end
95
+ message = msg.match SIWE_MESSAGE
96
+
97
+ raise Siwe::UnableToParseMessage unless message.to_s == msg
98
+
99
+ new(
100
+ message[:domain],
101
+ message[:address],
102
+ message[:uri],
103
+ message[:version],
104
+ {
105
+ statement: message[:statement],
106
+ issued_at: message[:issued_at],
107
+ nonce: message[:nonce],
108
+ chain_id: message[:chain_id].to_i,
109
+ expiration_time: message[:expiration_time],
110
+ not_before: message[:not_before],
111
+ request_id: message[:request_id],
112
+ resources: message[:resources]&.split("\n- ")&.drop(1)
113
+ }
114
+ )
120
115
  end
121
116
 
122
117
  def to_json_string
@@ -156,15 +151,62 @@ module Siwe
156
151
  )
157
152
  end
158
153
 
159
- def validate(signature)
160
- raise Siwe::ExpiredMessage if !@expiration_time.empty? && Time.now.utc > Time.parse(@expiration_time)
161
- raise Siwe::NotValidMessage if !@not_before.empty? && Time.now.utc < Time.parse(@not_before)
154
+ def validate
155
+ # check domain
156
+ raise Siwe::InvalidDomain unless @domain.match %r{[^/?#]*} || @domain.empty?
157
+
158
+ # check address EIP-55
159
+ raise Siwe::InvalidAddress unless Eth::Address.new(@address).to_s.eql? @address
160
+
161
+ # check uri
162
+ raise Siwe::InvalidURI unless URI.parse(@uri)
163
+
164
+ # check version
165
+ raise Siwe::InvalidMessageVersion unless @version == "1"
166
+
167
+ # check if the nonce is alphanumeric and bigger then 8 characters
168
+ raise Siwe::InvalidNonce unless @nonce.match(%r{[a-zA-Z0-9]{8,}})
169
+
170
+ # check issued_at format
171
+ begin
172
+ Time.iso8601(@issued_at)
173
+ rescue ArgumentError
174
+ raise Siwe::InvalidTimeFormat, "issued_at"
175
+ end
176
+
177
+ # check exp_time
178
+ begin
179
+ Time.iso8601(@expiration_time) unless @expiration_time.nil? || @expiration_time.empty?
180
+ rescue ArgumentError
181
+ raise Siwe::InvalidTimeFormat, "expiration_time"
182
+ end
183
+
184
+ # check not_before
185
+ begin
186
+ Time.iso8601(@not_before) unless @not_before.nil? || @not_before.empty?
187
+ rescue ArgumentError
188
+ raise Siwe::InvalidTimeFormat, "not_before"
189
+ end
190
+
191
+ # check resources
192
+ raise Siwe::InvalidURI unless @resources.nil? || @resources.empty? || @resources.each { |uri| URI.parse(uri) }
193
+ end
194
+
195
+ def verify(signature, domain, time, nonce)
196
+ raise Siwe::DomainMismatch unless domain.nil? || domain.eql?(@domain)
197
+
198
+ raise Siwe::NonceMismatch unless nonce.nil? || nonce.eql?(@nonce)
199
+
200
+ check_time = time.nil? ? Time.now.utc : Time.iso8601(time)
201
+
202
+ raise Siwe::ExpiredMessage if (!@expiration_time.nil? && !@expiration_time.empty?) && check_time > Time.iso8601(@expiration_time)
203
+
204
+ raise Siwe::NotValidMessage if (!@not_before.nil? && !@not_before.empty?) && check_time < Time.iso8601(@not_before)
162
205
 
163
- raise Siwe::InvalidSignature if signature.empty?
206
+ raise Siwe::InvalidSignature if signature.nil? && signature.empty?
164
207
 
165
208
  raise Siwe::InvalidAddress unless @address.eql?(Eth::Address.new(@address).to_s)
166
209
 
167
- puts "whatever"
168
210
  begin
169
211
  pub_key = Eth::Signature.personal_recover prepare_message, signature
170
212
  signature_address = Eth::Util.public_key_to_address pub_key
@@ -184,7 +226,7 @@ module Siwe
184
226
 
185
227
  header = [greeting, address]
186
228
 
187
- if @statement.empty?
229
+ if @statement.nil? || @statement.empty?
188
230
  header.push "\n"
189
231
  else
190
232
  header.push statement
@@ -203,7 +245,6 @@ module Siwe
203
245
  expiration_time = "Expiration Time: #{@expiration_time}"
204
246
  not_before = "Not Before: #{@not_before}"
205
247
  request_id = "Request ID: #{@request_id}"
206
- resources = "Resources:\n#{@resources.map { |x| "- #{x}" }.join "\n"}"
207
248
 
208
249
  body.push expiration_time unless @expiration_time.to_s.strip.empty?
209
250
 
@@ -211,7 +252,7 @@ module Siwe
211
252
 
212
253
  body.push request_id unless @request_id.to_s.strip.empty?
213
254
 
214
- body.push resources unless @resources.empty?
255
+ body.push "Resources:\n#{@resources.map { |x| "- #{x}" }.join "\n"}" unless @resources.nil? || @resources.empty?
215
256
 
216
257
  body = body.join "\n"
217
258
 
data/lib/siwe/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Siwe
4
- VERSION = "1.1.2"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/siwe.rb CHANGED
@@ -7,9 +7,17 @@ module Siwe
7
7
  autoload :Message, "siwe/message"
8
8
  autoload :Util, "siwe/util"
9
9
  autoload :ExpiredMessage, "siwe/exceptions"
10
+ autoload :InvalidDomain, "siwe/exceptions"
11
+ autoload :DomainMismatch, "siwe/exceptions"
12
+ autoload :NonceMismatch, "siwe/exceptions"
13
+ autoload :InvalidAddress, "siwe/exceptions"
14
+ autoload :InvalidURI, "siwe/exceptions"
15
+ autoload :InvalidNonce, "siwe/exceptions"
10
16
  autoload :NotValidMessage, "siwe/exceptions"
17
+ autoload :InvalidTimeFormat, "siwe/exceptions"
18
+ autoload :InvalidMessageVersion, "siwe/exceptions"
11
19
  autoload :InvalidSignature, "siwe/exceptions"
12
- autoload :InvalidAddress, "siwe/exceptions"
20
+ autoload :UnableToParseMessage, "siwe/exceptions"
13
21
 
14
22
  class Error < StandardError; end
15
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: siwe
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Spruce Systems Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-07 00:00:00.000000000 Z
11
+ date: 2022-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eth