Class: Siwe::Message
- Inherits:
-
Object
- Object
- Siwe::Message
- Defined in:
- lib/siwe/message.rb
Overview
Class that defines the EIP-4361 message fields and some utility methods to generate/validate the messages
Instance Attribute Summary collapse
-
#address ⇒ Object
Ethereum address performing the signing conformant to capitalization encoded checksum specified in EIP-55 where applicable.
-
#chain_id ⇒ Object
EIP-155 Chain ID to which the session is bound, and the network where Contract Accounts must be resolved.
-
#domain ⇒ Object
RFC 4501 dns authority that is requesting the signing.
-
#expiration_time ⇒ Object
ISO 8601 datetime string that, if present, indicates when the signed authentication message is no longer valid.
-
#issued_at ⇒ Object
ISO 8601 datetime string of the current time.
-
#nonce ⇒ Object
Randomized token used to prevent replay attacks, at least 8 alphanumeric characters.
-
#not_before ⇒ Object
ISO 8601 datetime string that, if present, indicates when the signed authentication message will become valid.
-
#request_id ⇒ Object
System-specific identifier that may be used to uniquely refer to the sign-in request.
-
#resources ⇒ Object
List of information or references to information the user wishes to have resolved as part of authentication by the relying party.
-
#statement ⇒ Object
Human-readable ASCII assertion that the user will sign, and it must not contain ‘n`.
-
#uri ⇒ Object
RFC 3986 URI referring to the resource that is the subject of the signing (as in the __subject__ of a claim).
-
#version ⇒ Object
Current version of the message.
Class Method Summary collapse
Instance Method Summary collapse
-
#initialize(domain, address, uri, version, options = {}) ⇒ Message
constructor
A new instance of Message.
- #prepare_message ⇒ Object
- #to_json_string ⇒ Object
- #validate ⇒ Object
- #verify(signature, domain, time, nonce) ⇒ Object
Constructor Details
#initialize(domain, address, uri, version, options = {}) ⇒ Message
Returns a new instance of Message.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/siwe/message.rb', line 78 def initialize(domain, address, uri, version, = {}) @domain = domain @address = address @uri = uri @version = version @statement = .fetch :statement, "" @issued_at = .fetch :issued_at, Time.now.utc.iso8601 @nonce = .fetch :nonce, Siwe::Util.generate_nonce @chain_id = .fetch :chain_id, 1 @expiration_time = .fetch :expiration_time, "" @not_before = .fetch :not_before, "" @request_id = .fetch :request_id, "" @resources = .fetch :resources, [] validate end |
Instance Attribute Details
#address ⇒ Object
Ethereum address performing the signing conformant to capitalization encoded checksum specified in EIP-55 where applicable.
37 38 39 |
# File 'lib/siwe/message.rb', line 37 def address @address end |
#chain_id ⇒ Object
EIP-155 Chain ID to which the session is bound, and the network where Contract Accounts must be resolved.
48 49 50 |
# File 'lib/siwe/message.rb', line 48 def chain_id @chain_id end |
#domain ⇒ Object
RFC 4501 dns authority that is requesting the signing.
33 34 35 |
# File 'lib/siwe/message.rb', line 33 def domain @domain end |
#expiration_time ⇒ Object
ISO 8601 datetime string that, if present, indicates when the signed authentication message is no longer valid.
63 64 65 |
# File 'lib/siwe/message.rb', line 63 def expiration_time @expiration_time end |
#issued_at ⇒ Object
ISO 8601 datetime string of the current time.
55 56 57 |
# File 'lib/siwe/message.rb', line 55 def issued_at @issued_at end |
#nonce ⇒ Object
Randomized token used to prevent replay attacks, at least 8 alphanumeric characters.
52 53 54 |
# File 'lib/siwe/message.rb', line 52 def nonce @nonce end |
#not_before ⇒ Object
ISO 8601 datetime string that, if present, indicates when the signed authentication message will become valid.
67 68 69 |
# File 'lib/siwe/message.rb', line 67 def not_before @not_before end |
#request_id ⇒ Object
System-specific identifier that may be used to uniquely refer to the sign-in request.
71 72 73 |
# File 'lib/siwe/message.rb', line 71 def request_id @request_id end |
#resources ⇒ Object
List of information or references to information the user wishes to have resolved as part of authentication by the relying party. They are expressed as RFC 3986 URIs separated by ‘n- `.
76 77 78 |
# File 'lib/siwe/message.rb', line 76 def resources @resources end |
#statement ⇒ Object
Human-readable ASCII assertion that the user will sign, and it must not contain ‘n`.
59 60 61 |
# File 'lib/siwe/message.rb', line 59 def statement @statement end |
#uri ⇒ Object
RFC 3986 URI referring to the resource that is the subject of the signing (as in the __subject__ of a claim).
41 42 43 |
# File 'lib/siwe/message.rb', line 41 def uri @uri end |
#version ⇒ Object
Current version of the message.
44 45 46 |
# File 'lib/siwe/message.rb', line 44 def version @version end |
Class Method Details
.from_json_string(str) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/siwe/message.rb', line 135 def self.from_json_string(str) obj = JSON.parse str, { symbolize_names: true } Siwe::Message.new( obj[:domain], obj[:address], obj[:uri], obj[:version], { chain_id: obj[:chain_id], nonce: obj[:nonce], issued_at: obj[:issued_at], statement: obj[:statement], expiration_time: obj[:expiration_time], not_before: obj[:not_before], request_id: obj[:request_id], resources: obj[:resources] } ) end |
.from_message(msg) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/siwe/message.rb', line 94 def self.(msg) = msg.match SIWE_MESSAGE raise Siwe::UnableToParseMessage unless .to_s == msg new( [:domain], [:address], [:uri], [:version], { statement: [:statement], issued_at: [:issued_at], nonce: [:nonce], chain_id: [:chain_id].to_i, expiration_time: [:expiration_time], not_before: [:not_before], request_id: [:request_id], resources: [:resources]&.split("\n- ")&.drop(1) } ) end |
Instance Method Details
#prepare_message ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/siwe/message.rb', line 222 def greeting = "#{@domain} wants you to sign in with your Ethereum account:" address = @address statement = "\n#{@statement}\n" header = [greeting, address] if @statement.nil? || @statement.empty? header.push "\n" else header.push statement end header = header.join "\n" uri = "URI: #{@uri}" version = "Version: #{@version}" chain_id = "Chain ID: #{@chain_id}" nonce = "Nonce: #{@nonce}" issued_at = "Issued At: #{@issued_at}" body = [uri, version, chain_id, nonce, issued_at] expiration_time = "Expiration Time: #{@expiration_time}" not_before = "Not Before: #{@not_before}" request_id = "Request ID: #{@request_id}" body.push expiration_time unless @expiration_time.to_s.strip.empty? body.push not_before unless @not_before.to_s.strip.empty? body.push request_id unless @request_id.to_s.strip.empty? body.push "Resources:\n#{@resources.map { |x| "- #{x}" }.join "\n"}" unless @resources.nil? || @resources.empty? body = body.join "\n" [header, body].join "\n" end |
#to_json_string ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/siwe/message.rb', line 117 def to_json_string obj = { domain: @domain, address: Eth::Address.new(@address).to_s, uri: @uri, version: @version, chain_id: @chain_id, nonce: @nonce, issued_at: @issued_at, statement: @statement, expiration_time: @expiration_time, not_before: @not_before, request_id: @request_id, resources: @resources } obj.to_json end |
#validate ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/siwe/message.rb', line 154 def validate # check domain raise Siwe::InvalidDomain unless @domain.match %r{[^/?#]*} || @domain.empty? # check address EIP-55 raise Siwe::InvalidAddress unless Eth::Address.new(@address).to_s.eql? @address # check uri raise Siwe::InvalidURI unless URI.parse(@uri) # check version raise Siwe::InvalidMessageVersion unless @version == "1" # check if the nonce is alphanumeric and bigger then 8 characters raise Siwe::InvalidNonce unless @nonce.match(%r{[a-zA-Z0-9]{8,}}) # check issued_at format begin Time.iso8601(@issued_at) rescue ArgumentError raise Siwe::InvalidTimeFormat, "issued_at" end # check exp_time begin Time.iso8601(@expiration_time) unless @expiration_time.nil? || @expiration_time.empty? rescue ArgumentError raise Siwe::InvalidTimeFormat, "expiration_time" end # check not_before begin Time.iso8601(@not_before) unless @not_before.nil? || @not_before.empty? rescue ArgumentError raise Siwe::InvalidTimeFormat, "not_before" end # check resources raise Siwe::InvalidURI unless @resources.nil? || @resources.empty? || @resources.each { |uri| URI.parse(uri) } end |
#verify(signature, domain, time, nonce) ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/siwe/message.rb', line 195 def verify(signature, domain, time, nonce) raise Siwe::DomainMismatch unless domain.nil? || domain.eql?(@domain) raise Siwe::NonceMismatch unless nonce.nil? || nonce.eql?(@nonce) check_time = time.nil? ? Time.now.utc : Time.iso8601(time) raise Siwe::ExpiredMessage if (!@expiration_time.nil? && !@expiration_time.empty?) && check_time > Time.iso8601(@expiration_time) raise Siwe::NotValidMessage if (!@not_before.nil? && !@not_before.empty?) && check_time < Time.iso8601(@not_before) raise Siwe::InvalidSignature if signature.nil? && signature.empty? raise Siwe::InvalidAddress unless @address.eql?(Eth::Address.new(@address).to_s) begin pub_key = Eth::Signature.personal_recover , signature signature_address = Eth::Util.public_key_to_address pub_key rescue StandardError raise Siwe::InvalidSignature end raise Siwe::InvalidSignature unless signature_address.to_s.downcase.eql? @address.to_s.downcase true end |