Class: Siwe::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/siwe/message.rb

Overview

An EIP-4361 Sign-In with Ethereum message. Construct via ‘Siwe::Message.new(**fields)` or parse via `Siwe::Message.parse(string)`. Render via `#prepare_message` (alias `#to_eip4361`, `#to_s`).

Constant Summary collapse

FIELDS =
%i[
  scheme domain address statement uri version chain_id nonce
  issued_at expiration_time not_before request_id resources
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(domain:, address:, uri:, chain_id:, nonce: nil, version: "1", scheme: nil, statement: nil, issued_at: nil, expiration_time: nil, not_before: nil, request_id: nil, resources: nil) ⇒ Message

Returns a new instance of Message.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/siwe/message.rb', line 38

def initialize(domain:, address:, uri:, chain_id:, nonce: nil, version: "1",
               scheme: nil, statement: nil, issued_at: nil,
               expiration_time: nil, not_before: nil, request_id: nil, resources: nil)
  @warnings = []
  @scheme = scheme
  @domain = domain
  @address = normalize_address(address)
  @statement = statement
  @uri = uri
  @version = version
  @chain_id = coerce_chain_id(chain_id)
  @nonce = nonce || Util.generate_nonce
  @issued_at = issued_at || Time.now.utc.iso8601
  @expiration_time = expiration_time
  @not_before = not_before
  @request_id = request_id
  @resources = resources

  validate_required!
  roundtrip_validate!
  freeze
end

Class Method Details

.from_json(json_str) ⇒ Object



31
32
33
34
35
36
# File 'lib/siwe/message.rb', line 31

def self.from_json(json_str)
  data = JSON.parse(json_str, symbolize_names: true)
  new(**data.slice(*FIELDS))
rescue JSON::ParserError => e
  raise Error.new(ErrorType::UNABLE_TO_PARSE, message: "Invalid JSON: #{e.message}")
end

.parse(str) ⇒ Object



24
25
26
27
28
29
# File 'lib/siwe/message.rb', line 24

def self.parse(str)
  result = Parser.parse(str)
  msg = allocate
  msg.send(:init_from_parser, result)
  msg
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



121
122
123
# File 'lib/siwe/message.rb', line 121

def ==(other)
  other.is_a?(Message) && to_h == other.to_h
end

#hashObject



126
127
128
# File 'lib/siwe/message.rb', line 126

def hash
  to_h.hash
end

#prepare_messageObject Also known as: to_eip4361, to_s



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/siwe/message.rb', line 61

def prepare_message
  header_prefix = @scheme ? "#{@scheme}://#{@domain}" : @domain
  header = "#{header_prefix} wants you to sign in with your Ethereum account:"

  prefix = "#{header}\n#{@address}"
  prefix = if @statement.nil?
             "#{prefix}\n\n"
           else
             "#{prefix}\n\n#{@statement}\n"
           end

  suffix = "URI: #{@uri}\nVersion: #{@version}\nChain ID: #{@chain_id}"
  suffix << "\nNonce: #{@nonce}"
  suffix << "\nIssued At: #{@issued_at}" if @issued_at
  suffix << "\nExpiration Time: #{@expiration_time}" if @expiration_time
  suffix << "\nNot Before: #{@not_before}" if @not_before
  suffix << "\nRequest ID: #{@request_id}" unless @request_id.nil?
  if @resources
    suffix << "\nResources:"
    @resources.each { |r| suffix << "\n- #{r}" }
  end

  "#{prefix}\n#{suffix}"
end

#to_hObject



113
114
115
# File 'lib/siwe/message.rb', line 113

def to_h
  FIELDS.to_h { |f| [f, instance_variable_get("@#{f}")] }
end

#to_jsonObject



117
118
119
# File 'lib/siwe/message.rb', line 117

def to_json(*)
  to_h.to_json(*)
end

#verify(signature:, domain:, nonce:, scheme: nil, uri: nil, chain_id: nil, request_id: nil, time: nil, config: nil, strict: false) ⇒ Object

Verify a signature against this message and the verification params. Returns Siwe::Response — never raises on verification failure.



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/siwe/message.rb', line 91

def verify(signature:, domain:, nonce:, scheme: nil, uri: nil, chain_id: nil,
           request_id: nil, time: nil, config: nil, strict: false)
  cfg = config || Siwe.config

  check_param_mismatches!(domain: domain, nonce: nonce, scheme: scheme,
                          uri: uri, chain_id: chain_id, request_id: request_id, strict: strict)
  check_temporal!(time)
  check_signature!(signature, cfg)

  Response.new(success: true, error: nil, data: self)
rescue Error => e
  Response.new(success: false, error: e, data: self)
end

#verify!Object

Verify a signature; raises Siwe::Error on failure, returns self on success.



106
107
108
109
110
111
# File 'lib/siwe/message.rb', line 106

def verify!(**)
  response = verify(**)
  raise response.error if response.failure?

  self
end