silkey-sdk 0.0.5 → 0.1.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: eeeeebc639645d5bcc3e594cd5c65348b2feecd7036b45d861ba236b33812f49
4
- data.tar.gz: 01eaf4cfb5e687676bc423b95d96557aeb770ec242aa9ecb6c22552b46b37ae2
3
+ metadata.gz: ac39da25efe706b56abfda8f580295dc0d0c7734ce83ccc13130b85cce5a1e22
4
+ data.tar.gz: 86744220f6d4e5ee274c1c451cc5be9591a60f240a5a2fc871d5fc3794d71988
5
5
  SHA512:
6
- metadata.gz: a8f1471fdb9264ff918ceb15a4dec2ad794263611a1d814ca107f8f15c0e6e26f392dbc5afa3e0a8f431532f3e019cf784f4aac96998ab3832da1d3325a481e4
7
- data.tar.gz: 4658e8d81cc57b104e634c091358519956a6b271af4f36d3a19f811f2fceaa76feacd29917dd5f554d278f2b25f56f5412b7ef6afe5baa652a03c3f25435b94f
6
+ metadata.gz: b634f6d573b002484a32a02059b730b5cd9184d93de5aaa67d9df981d833269845df561394444d239ea327ac653e68d2ffda4fa36a05e680b73e34d67384a065
7
+ data.tar.gz: 4024009b029a6d8aa45b7beb9d79a2d61c677d761d94ba79234539dc1aac9fe43e729735bd2df58b86cdfdcb8085c2fbbf70018624fbbb5504d9c85132c0ed22
@@ -32,7 +32,7 @@ Metrics/BlockLength:
32
32
  ExcludedMethods: ['describe', 'context', 'let']
33
33
 
34
34
  Layout/LineLength:
35
- Max: 100
35
+ Max: 120
36
36
 
37
37
  Metrics/ClassLength:
38
38
  Max: 150
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.1.0] - 2021-01-03
8
+ ### Added
9
+ - additional verification for token:
10
+ - verify token age
11
+ - verify sso params
12
+ - website signature
13
+ - support for migration
14
+ - settings model for jwt and sso params
15
+
16
+ ### Changed
17
+ - all SSO params have prefix `sso`
18
+
19
+ ### Removed
20
+ - `refId` is not longer part of token
21
+
7
22
  ## [0.0.5] - 2020-12-15
8
23
  ### Changed
9
24
  - add `0x` to signature to be compatible with JS and not throw `signature missing v and recoveryParam` error
@@ -47,6 +47,7 @@ Silkey::RegistryContract.get_address('Name')
47
47
  ```bash
48
48
  bundle exec rspec
49
49
  bundle exec rubocop --fix
50
+ bundle exec rubocop --auto-correct-all
50
51
  ```
51
52
 
52
53
  #### Init setup environment
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- silkey-sdk (0.0.5)
4
+ silkey-sdk (0.1.0)
5
5
  activesupport (~> 6.0)
6
6
  eth (~> 0.4.12)
7
7
  ethereum.rb (~> 2.5)
data/README.md CHANGED
@@ -27,31 +27,43 @@ end
27
27
 
28
28
  #### Making request
29
29
 
30
- | Parameter | Required | Type | Desc
31
- | ---------------- |:---------:| -------- | -----
32
- | signature | yes | string | Domain owner signature
33
- | ssoTimestamp | yes | number | Time of signing SSO request
34
- | redirectUrl | yes | string | Where to redirect user with token after sign in
35
- | cancelUrl | yes | string | Where to redirect user on error
36
- | redirectMethod | no | GET/POST | How to redirect user after sign in, default is POST
37
- | refId | no | string | It will be return with user token, you may use it to identify request
38
- | scope | no | string | Scope of data to return in a token payload: `id` (default) returns only user address, `email` returns address + email
30
+ | Parameter | Required | Type | Desc
31
+ | ----------------- |:---------:| -------- | -----
32
+ | ssoSignature | yes | string | Domain owner signature
33
+ | ssoTimestamp | yes | number | Time of signing SSO request
34
+ | ssoRedirectUrl | yes | string | Where to redirect user with token after sign in
35
+ | ssoCancelUrl | yes | string | Where to redirect user on error
36
+ | ssoRedirectMethod | no | GET/POST | How to redirect user after sign in, default is POST
37
+ | ssoRefId | no | string | Any value, you may use it to identify request
38
+ | ssoScope | no | string | Scope of data to return in a token payload: `id` (default) returns only user address, `email` returns address + email
39
39
 
40
40
 
41
41
  ```rb
42
- params = { redirectUrl: 'https://your-website', refId: '12ab' }
42
+ params = { ssoRedirectUrl: 'https://your-website', ssoRefId: '12ab' }
43
43
  sso_params = Silkey::SDK.generate_sso_request_params(private_key, params)
44
44
  ```
45
45
 
46
46
  #### On request callback page
47
47
 
48
- `token` - get if from request params (it can be send via POST or GET, based on `redirectMethod`)
48
+ Callback will be done via POST (default) or GET, based on `ssoRedirectMethod`.
49
+
50
+ Callback params contains:
51
+ - sso parameters that were used to make SSO call
52
+ - `token`.
53
+
54
+ `token` - get if from request params
55
+
56
+ `ssoRequestParams` - get if from request params (it can be send via POST or GET, based on `ssoRedirectMethod`)
49
57
 
50
58
  ```rb
51
- silkey_public_key = Silkey::SDK.fetch_silkey_public_key
52
- Silkey::SDK.token_payload_verifier(token, silkey_public_key)
59
+ silkey_eth_address = Silkey::SDK.fetch_silkey_eth_address
60
+ Silkey::SDK.token_payload_verifier(token, silkey_eth_address)
53
61
  ```
54
62
 
63
+ ## Recommendations and Migration
64
+
65
+ See [recommendation and migration](https://github.com/Silkey-Team/silkey-sdk#recommendations) sections on main SDK package.
66
+
55
67
  ## License
56
68
 
57
69
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,14 +1,10 @@
1
1
  require 'eth'
2
2
  require 'ethereum.rb'
3
- # require 'json'
4
3
  require 'jwt'
5
4
  require 'logger'
6
5
  require 'pry'
7
- #require 'active_support'
8
- #require 'active_support/core_ext/module'
9
6
  require 'virtus_convert'
10
7
  require 'virtus'
11
- # require 'retriable'
12
8
  require 'silkey'
13
9
 
14
10
  require_relative 'silkey/contract'
@@ -16,7 +12,10 @@ require_relative 'silkey/configuration'
16
12
  require_relative 'silkey/factories/client_factory'
17
13
  require_relative 'silkey/factories/contract_factory'
18
14
  require_relative 'silkey/models/jwt_payload'
15
+ require_relative 'silkey/models/settings'
16
+ require_relative 'silkey/models/sso_params'
19
17
  require_relative 'silkey/services/logger_service'
18
+ require_relative 'silkey/services/verifier'
20
19
  require_relative 'silkey/sdk'
21
20
  require_relative 'silkey/utils'
22
21
 
@@ -2,28 +2,36 @@
2
2
 
3
3
  module Silkey
4
4
  module Models
5
+ ##
6
+ # Generates message to sign based on plain object data (keys => values)
7
+ #
8
+ # @param email [string] verified email of the user
9
+ # IMPORTANT: if email in user profile is different, you should always update it with this one.
10
+ #
11
+ # @param scope [string]
12
+ # @param address [string] ID of the user, this is also valid ethereum address, use this to identify user
13
+ # @param userSignature [string] proof that request came from the user
14
+ # @param userSignatureTimestamp [number] time when signature was crated
15
+ # @param silkeySignature [string] proof that Silkey verified the email
16
+ # @param silkeySignatureTimestamp [number] time when signature was crated
17
+ # @param migration [boolean] true if user started migration to Silkey
18
+ #
5
19
  class JwtPayload
6
20
  include Virtus.model
7
21
 
8
- SCOPE_DIVIDER = ','
9
-
10
22
  # rubocop:disable Style/HashSyntax
11
- attribute :address, String, :writer => :private
12
23
  attribute :email, String, :writer => :private
24
+ attribute :_scope, {}, :writer => :private, :reader => :private
25
+ attribute :address, String, :writer => :private
13
26
  attribute :silkey_signature, String, :writer => :private
14
- attribute :silkey_signature_timestamp, Integer
27
+ attribute :silkey_signature_timestamp, Integer, :default => 0
15
28
  attribute :user_signature, String, :writer => :private
16
- attribute :user_signature_timestamp, Integer
17
- attribute :ref_id, String, :writer => :private
18
- attribute :_scope, {}, :writer => :private, :reader => :private
29
+ attribute :user_signature_timestamp, Integer, :default => 0
30
+ attribute :migration, Boolean, :writer => :private, :default => false
19
31
  # rubocop:enable Style/HashSyntax
20
32
 
21
33
  def scope
22
- _scope.keys.sort.join(SCOPE_DIVIDER)
23
- end
24
-
25
- def scope_divider
26
- SCOPE_DIVIDER
34
+ _scope.keys.sort.join(Silkey::Settings.SCOPE_DIVIDER)
27
35
  end
28
36
 
29
37
  # rubocop:disable Naming/AccessorMethodName
@@ -46,8 +54,8 @@ module Silkey
46
54
  self
47
55
  end
48
56
 
49
- def set_ref_id(ref_id)
50
- self.ref_id = ref_id
57
+ def set_migrations(migrating)
58
+ self.migration = migrating
51
59
  self
52
60
  end
53
61
 
@@ -70,17 +78,22 @@ module Silkey
70
78
  end
71
79
  # rubocop:enable Naming/AccessorMethodName
72
80
 
81
+ # rubocop:disable Metrics/AbcSize
82
+ #
73
83
  ##
74
84
  # Creates message that's need to be sign by user
75
85
  def message_to_sign_by_user
76
- if !Silkey::Utils.empty?(address) && Silkey::Utils.empty?(user_signature_timestamp)
77
- self.user_signature_timestamp = Silkey::Utils.current_timestamp
78
- end
79
-
80
- return pack_payload_to_hex if Silkey::Utils.empty?(user_signature_timestamp)
81
-
82
- "#{pack_payload_to_hex}#{Silkey::Utils.int_to_hex(user_signature_timestamp.to_s)}"
86
+ data = {
87
+ address: Silkey::Utils.strings_to_hex(['address']) + Silkey::Utils.remove0x(address).downcase,
88
+ migration: Silkey::Utils.strings_to_hex(['migration']) + (migration ? '01' : '00'),
89
+ scope: Silkey::Utils.strings_to_hex(['scope', scope]),
90
+ userSignatureTimestamp: Silkey::Utils.strings_to_hex(['userSignatureTimestamp']) +
91
+ Silkey::Utils.int_to_hex(user_signature_timestamp)
92
+ }
93
+
94
+ data.keys.sort.map { |k| data[k] }.join('')
83
95
  end
96
+ # rubocop:enable Metrics/AbcSize
84
97
 
85
98
  def message_to_sign_by_silkey
86
99
  return '' if Silkey::Utils.empty?(email)
@@ -89,33 +102,28 @@ module Silkey
89
102
  self.silkey_signature_timestamp = Silkey::Utils.current_timestamp
90
103
  end
91
104
 
92
- str_hex = [
93
- 'email', email,
94
- 'silkeySignatureTimestamp'
95
- ].map { |str| str.to_s.unpack('H*') }.join('')
96
-
97
- "#{str_hex}#{Silkey::Utils.int_to_hex(silkey_signature_timestamp.to_s)}"
105
+ "#{email.to_s.unpack('H*')[0]}#{Silkey::Utils.int_to_hex(silkey_signature_timestamp)}"
98
106
  end
99
107
 
100
108
  def validate
101
109
  raise "address is invalid: #{address}" unless Silkey::Utils.ethereum_address?(address)
102
110
 
103
- unless Silkey::Utils.signature?(user_signature)
104
- raise "user_signature is invalid: #{user_signature}"
105
- end
111
+ raise "user_signature is invalid: #{user_signature}" unless Silkey::Utils.signature?(user_signature)
106
112
 
107
- raise 'user_signature_timestamp is empty' if Silkey::Utils.empty?(user_signature_timestamp)
113
+ raise 'user_signature_timestamp is invalid' unless Silkey::Utils.timestamp?(user_signature_timestamp)
108
114
 
109
115
  return self if Silkey::Utils.empty?(scope) || scope == 'id'
110
116
 
111
117
  validate_scope_email
112
118
  end
113
119
 
114
- def import(hash)
115
- hash.each do |k, v|
120
+ def import(data = {})
121
+ return data if data.is_a?(Silkey::Models::JwtPayload)
122
+
123
+ data.each do |k, v|
116
124
  var = k.to_s.underscore
117
125
 
118
- if k == 'scope'
126
+ if var == 'scope'
119
127
  set_scope(v)
120
128
  else
121
129
  instance_variable_set("@#{var}", v)
@@ -132,7 +140,6 @@ module Silkey
132
140
  adr_hex = Silkey::Utils.remove0x(address).downcase
133
141
 
134
142
  str2_hex = [
135
- 'refId', ref_id.to_s,
136
143
  'scope', scope,
137
144
  'userSignatureTimestamp'
138
145
  ].map { |str| str.to_s.unpack('H*') }.join('')
@@ -143,13 +150,9 @@ module Silkey
143
150
  def validate_scope_email
144
151
  raise 'email is empty' if Silkey::Utils.empty?(email)
145
152
 
146
- unless Silkey::Utils.signature?(silkey_signature)
147
- raise "silkey_signature is invalid: #{silkey_signature}"
148
- end
153
+ raise "silkey_signature is invalid: #{silkey_signature}" unless Silkey::Utils.signature?(silkey_signature)
149
154
 
150
- if Silkey::Utils.empty?(silkey_signature_timestamp)
151
- raise 'silkey_signature_timestamp is empty'
152
- end
155
+ raise 'silkey_signature_timestamp is invalid' unless Silkey::Utils.timestamp?(silkey_signature_timestamp)
153
156
 
154
157
  self
155
158
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Silkey # :nodoc: all
4
+ class Settings
5
+ # rubocop:disable Naming/MethodName
6
+ class << self
7
+ attr_accessor :SSO_PARAMS
8
+ attr_accessor :JWT
9
+ attr_accessor :MESSAGE_TO_SIGN_GLUE
10
+ attr_accessor :SCOPE_DIVIDER
11
+ attr_accessor :SILKEY_REGISTERED_BY_NAME
12
+ end
13
+ # rubocop:enable Naming/MethodName
14
+
15
+ self.SSO_PARAMS = {
16
+ required: %w(ssoSignature ssoRedirectUrl ssoCancelUrl ssoTimestamp),
17
+ optional: %w(ssoRefId ssoScope ssoRedirectMethod),
18
+ prefix: 'sso'
19
+ }
20
+
21
+ self.JWT = {
22
+ id: {
23
+ required: %w(address)
24
+ },
25
+ email: {
26
+ required: %w(address email)
27
+ }
28
+ }
29
+
30
+ self.MESSAGE_TO_SIGN_GLUE = '::'
31
+ self.SCOPE_DIVIDER = ','
32
+ self.SILKEY_REGISTERED_BY_NAME = 'Hades'
33
+ end
34
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Silkey
4
+ module Models
5
+ class SSOParams
6
+ attr_accessor :params
7
+
8
+ def initialize(params = {})
9
+ @params = params
10
+ end
11
+
12
+ def sign(private_key)
13
+ params[:ssoTimestamp] = Utils.current_timestamp unless Utils.timestamp?(params[:ssoTimestamp])
14
+
15
+ params[:ssoSignature] = Silkey::Utils.sign_message(private_key, SDK.message_to_sign(params))
16
+
17
+ self
18
+ end
19
+
20
+ def required_present?
21
+ Silkey::Settings.SSO_PARAMS[:required].all? do |k|
22
+ if Silkey::Utils.empty?(params[k.to_sym])
23
+ Silkey::LoggerService.warn(
24
+ "Missing #{k}. This parameters are required for Silkey SSO: " +
25
+ Silkey::Settings.SSO_PARAMS[:required].join(', ')
26
+ )
27
+
28
+ return false
29
+ end
30
+
31
+ true
32
+ end
33
+ end
34
+
35
+ def validate
36
+ raise 'Missing required params' unless required_present?
37
+
38
+ self
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,6 +3,8 @@
3
3
  module Silkey
4
4
  class SDK
5
5
  class << self
6
+ SSO_PARAMS_PREFIX = 'sso'
7
+
6
8
  ##
7
9
  # Generates message to sign based on plain object data (keys => values)
8
10
  #
@@ -12,20 +14,22 @@ module Silkey
12
14
  #
13
15
  # @example:
14
16
  #
15
- # Silkey::SDK.message_to_sign({ redirectUrl: 'http://silkey.io', refId: 1 });
17
+ # Silkey::SDK.message_to_sign({ ssoRedirectUrl: 'http://silkey.io', ssoCancelUrl: 'http://silkey.io/fail' });
16
18
  #
17
19
  # returns
18
20
  #
19
- # 'redirectUrl=http://silkey.io::refId=1'
21
+ # 'ssoRedirectUrl=http://silkey.io::ssoCancelUrl=http://silkey.io/fail'
20
22
  #
21
23
  def message_to_sign(to_sign = {})
22
24
  msg = []
23
25
 
24
26
  to_sign.keys.sort.each do |k|
25
- msg.push("#{k}=#{to_sign[k]}") unless to_sign[k].nil?
27
+ if 'ssoSignature'.to_sym != k && k[0..SSO_PARAMS_PREFIX.length - 1] == SSO_PARAMS_PREFIX && !to_sign[k].nil?
28
+ msg.push("#{k}=#{to_sign[k]}")
29
+ end
26
30
  end
27
31
 
28
- msg.join('&')
32
+ msg.join(Silkey::Settings.MESSAGE_TO_SIGN_GLUE)
29
33
  end
30
34
 
31
35
  ##
@@ -33,12 +37,11 @@ module Silkey
33
37
  #
34
38
  # @param private_key [string] secret private key of domain owner
35
39
  #
36
- # @param result [Hash] Hash object with parameters:
37
- # - redirectUrl*,
38
- # - cancelUrl*,
39
- # - redirectMethod,
40
- # - refId,
41
- # - scope,
40
+ # @param data_to_sign [Hash] Hash object with parameters:
41
+ # - ssoRedirectUrl*,
42
+ # - ssoCancelUrl*,
43
+ # - ssoRedirectMethod,
44
+ # - ssoScope,
42
45
  # - ssoTimestamp
43
46
  # marked with * are required by Silkey
44
47
  #
@@ -48,25 +51,13 @@ module Silkey
48
51
  #
49
52
  # @example
50
53
  #
51
- # data = { redirectUrl: 'https://your-website', refId: '12ab' }
54
+ # data = { ssoRedirectUrl: 'https://your-website', ssoRefId: '12ab' }
52
55
  # Silkey::SDK.generate_sso_request_params(private_key, data)
53
56
  #
54
57
  def generate_sso_request_params(private_key, data_to_sign)
55
- result = data_to_sign.clone
56
-
57
58
  raise '`private_key` is empty' if Silkey::Utils.empty?(private_key)
58
59
 
59
- assert_required_soo_params(result)
60
-
61
- if Silkey::Utils.empty?(result[:ssoTimestamp])
62
- result[:ssoTimestamp] = Silkey::Utils.current_timestamp
63
- end
64
-
65
- result[:scope] = 'id' if Silkey::Utils.empty?(result[:scope])
66
- message = message_to_sign(result)
67
- result[:signature] = Silkey::Utils.sign_message(private_key, message)
68
-
69
- result
60
+ Silkey::Models::SSOParams.new(data_to_sign.clone).sign(private_key).validate.params
70
61
  end
71
62
 
72
63
  ##
@@ -76,7 +67,13 @@ module Silkey
76
67
  #
77
68
  # @param token [string] JWT token returned by Silkey
78
69
  #
79
- # @param silkey_public_key [string] public ethereum address of Silkey
70
+ # @param callback_params [string] params used to do SSO call
71
+ #
72
+ # @param website_eth_address [string] public ethereum address of website owner
73
+ #
74
+ # @param silkey_eth_address [string] public ethereum address of Silkey
75
+ #
76
+ # @param expiration_time [number] expiration time of token in seconds
80
77
  #
81
78
  # @return [JwtPayload|null] null when signature(s) are invalid, otherwise token payload
82
79
  #
@@ -87,19 +84,33 @@ module Silkey
87
84
  # Silkey::SDK.token_payload_verifier(
88
85
  # 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG'\
89
86
  # '9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
90
- # Silkey::SDK.fetch_silkey_public_key
87
+ # {ssoSignature: '...', ...},
88
+ # website_eth_address,
89
+ # Silkey::SDK.fetch_silkey_eth_address
91
90
  # )
92
91
  #
93
- def token_payload_verifier(token, silkey_public_key = nil)
92
+ def token_payload_verifier(token,
93
+ callback_params,
94
+ website_eth_address,
95
+ silkey_eth_address = nil,
96
+ expiration_time = 30)
94
97
  payload = token_payload(token)
95
98
 
96
- if silkey_signature_valid?(payload, silkey_public_key) != false &&
97
- user_signature_valid?(payload) != false
98
- return Silkey::Models::JwtPayload.new.import(payload)
99
- end
99
+ return nil unless Verifier.valid_age?(payload, expiration_time)
100
100
 
101
- nil
102
- rescue # rubocop:disable Style/RescueStandardError
101
+ return nil if Silkey::Verifier.user_signature_valid?(payload) == false
102
+
103
+ return nil if Silkey::Verifier.silkey_signature_valid?(payload, silkey_eth_address) == false
104
+
105
+ return nil if Silkey::Verifier.website_signature_valid?(callback_params, website_eth_address) == false
106
+
107
+ jwt_payload = Silkey::Models::JwtPayload.new.import(payload)
108
+
109
+ Silkey::Verifier.require_params_for_scope(jwt_payload.scope, jwt_payload.attributes)
110
+
111
+ jwt_payload
112
+ rescue StandardError => e
113
+ logger.warn(e.full_message)
103
114
  nil
104
115
  end
105
116
 
@@ -109,121 +120,22 @@ module Silkey
109
120
  #
110
121
  # @see List of Silkey contracts addresses: https://github.com/Silkey-Team/silkey-sdk#silkey-sdk
111
122
  #
112
- def fetch_silkey_public_key
113
- public_key = Silkey::RegistryContract.get_address('Hades')
123
+ def fetch_silkey_eth_address
124
+ silkey_address = Silkey::RegistryContract.get_address(Silkey::Settings.SILKEY_REGISTERED_BY_NAME)
114
125
 
115
- raise "Invalid public key: #{public_key}" unless Silkey::Utils.ethereum_address?(public_key)
126
+ raise "Invalid silkey address: #{silkey_address}" unless Silkey::Utils.ethereum_address?(silkey_address)
116
127
 
117
- public_key
128
+ silkey_address
118
129
  end
119
130
 
120
131
  private
121
132
 
122
- def assert_required_soo_params(params)
123
- raise '`redirectUrl` is empty' if Silkey::Utils.empty?(params[:redirectUrl])
124
-
125
- raise '`cancelUrl` is empty' if Silkey::Utils.empty?(params[:cancelUrl])
126
- end
127
-
128
133
  def token_payload(token)
129
134
  # Set password to nil and validation to false otherwise this won't work
130
135
  decoded = JWT.decode token, nil, false
131
136
  decoded[0]
132
137
  end
133
138
 
134
- def can_validate_user_signature?(jwt_payload)
135
- if Silkey::Utils.empty?(jwt_payload.address)
136
- logger.warn('Verification failed, missing user address')
137
- return false
138
- end
139
-
140
- if Silkey::Utils.empty?(jwt_payload.user_signature)
141
- logger.warn('Verification failed, missing user signature')
142
- return false
143
- end
144
-
145
- if Silkey::Utils.empty?(jwt_payload.user_signature_timestamp)
146
- logger.warn('Verification failed, missing user signature timestamp ')
147
- return false
148
- end
149
-
150
- true
151
- end
152
-
153
- def user_signature_valid?(payload)
154
- jwt_payload = Silkey::Models::JwtPayload.new.import(payload)
155
-
156
- return false unless can_validate_user_signature?(jwt_payload)
157
-
158
- signer = Silkey::Utils
159
- .verify_message(jwt_payload.message_to_sign_by_user, jwt_payload.user_signature)
160
-
161
- success = signer == jwt_payload.address
162
-
163
- unless success
164
- logger.warn("user_signature_valid?: expect #{signer} to be equal #{jwt_payload.address}")
165
- end
166
-
167
- success
168
- rescue StandardError => e
169
- logger.error(e)
170
- false
171
- end
172
-
173
- def cant_validate_silky_signature?(jwt_payload)
174
- empty_email = Silkey::Utils.empty?(jwt_payload.email)
175
- empty_sig = Silkey::Utils.empty?(jwt_payload.silkey_signature)
176
-
177
- empty_email && empty_sig
178
- end
179
-
180
- def silkey_sig_check_requirements?(jwt_payload)
181
- empty_email = Silkey::Utils.empty?(jwt_payload.email)
182
- empty_sig = Silkey::Utils.empty?(jwt_payload.silkey_signature)
183
-
184
- if empty_email ^ empty_sig
185
- logger.warn('Verification failed, missing silkey signature or email')
186
- return false
187
- end
188
-
189
- if Silkey::Utils.empty?(jwt_payload.silkey_signature_timestamp)
190
- logger.warn('Verification failed, missing silkey signature timestamp')
191
- return false
192
- end
193
-
194
- true
195
- end
196
-
197
- def silkey_signature_valid?(payload, silkey_public_key)
198
- jwt_payload = Silkey::Models::JwtPayload.new.import(payload)
199
-
200
- return nil if cant_validate_silky_signature?(jwt_payload)
201
-
202
- return false unless silkey_sig_check_requirements?(jwt_payload)
203
-
204
- signer = Silkey::Utils.verify_message(
205
- jwt_payload.message_to_sign_by_silkey, jwt_payload.silkey_signature
206
- )
207
-
208
- if Silkey::Utils.empty?(silkey_public_key)
209
- logger.warn('You are using verification without checking silkey signature. '\
210
- 'We strongly recommended to turn on full verification. '\
211
- 'This option can be deprecated in the future')
212
- return true
213
- end
214
-
215
- success = signer == silkey_public_key
216
-
217
- unless success
218
- logger.warn("silkey_signature_valid?: expect #{signer} to be equal #{silkey_public_key}")
219
- end
220
-
221
- success
222
- rescue StandardError => e
223
- logger.error(e)
224
- false
225
- end
226
-
227
139
  def logger
228
140
  Silkey::LoggerService
229
141
  end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Silkey # :nodoc: all
4
+ class Verifier
5
+ class << self
6
+ def require_params_for_scope(scope, data)
7
+ scope_arr = scope_to_arr(scope)
8
+
9
+ scope_arr.each do |scope_key|
10
+ raise "scope `#{scope_key}` is not supported" unless Silkey::Settings.JWT.key?(scope_key.to_sym)
11
+
12
+ Silkey::Settings.JWT[scope_key.to_sym][:required].each do |item|
13
+ if Silkey::Utils.empty?(data[item.to_sym])
14
+ raise "`#{item}` parameter is required for selected scope: #{scope_key}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ def valid_age?(jwt_payload, expiration_time)
21
+ if expiration_time <= 0
22
+ logger.warn("You set token expiration time to #{expiration_time}. " \
23
+ 'That mean token age will not be verified. ' \
24
+ 'We strongly recommended to set expiration time between 5s - 60s for security reasons.')
25
+
26
+ return true
27
+ end
28
+
29
+ if expiration_time > 100
30
+ logger.warn("You set token expiration time to #{expiration_time}. " \
31
+ 'We strongly recommended to set expiration time between 5s - 30s for security reasons.')
32
+ end
33
+
34
+ age = Silkey::Utils.current_timestamp - jwt_payload.user_signature_timestamp
35
+
36
+ if age > expiration_time
37
+ logger.warn("token expired, expected age #{expiration_time}s but got #{age}s")
38
+ return false
39
+ end
40
+
41
+ if expiration_time.negative?
42
+ logger.warn('token from the future... https://www.youtube.com/watch?v=FWG3Dfss3Jc')
43
+ return false
44
+ end
45
+
46
+ true
47
+ end
48
+
49
+ ##
50
+ #
51
+ # @param sso_params [Hash|Silkey::Models::SSOParams.params] hash object
52
+ #
53
+ # @param website_owner_address [string]
54
+ #
55
+ # @return [boolean]
56
+ #
57
+ def website_signature_valid?(sso_params, website_owner_address)
58
+ if Silkey::Utils.empty?(sso_params[:ssoSignature])
59
+ Silkey::LoggerService.warn('ssoSignature is empty')
60
+ return false
61
+ end
62
+
63
+ message = Silkey::SDK.message_to_sign(sso_params)
64
+ signer = Silkey::Utils.verify_message(message, sso_params[:ssoSignature])
65
+
66
+ success = signer == website_owner_address
67
+
68
+ logger.warn("website_signature_valid?: expect #{signer} to be equal #{website_owner_address}") unless success
69
+
70
+ success
71
+ rescue StandardError => e
72
+ logger.error(e)
73
+ false
74
+ end
75
+
76
+ def user_signature_valid?(payload)
77
+ jwt_payload = Silkey::Models::JwtPayload.new.import(payload)
78
+
79
+ return false unless can_validate_user_signature?(jwt_payload)
80
+
81
+ signer = Silkey::Utils
82
+ .verify_message(jwt_payload.message_to_sign_by_user, jwt_payload.user_signature)
83
+
84
+ success = signer == jwt_payload.address
85
+
86
+ logger.warn("user_signature_valid?: expect #{signer} to be equal #{jwt_payload.address}") unless success
87
+
88
+ success
89
+ rescue StandardError => e
90
+ logger.error(e)
91
+ false
92
+ end
93
+
94
+ # rubocop:disable Metrics/AbcSize
95
+ def silkey_signature_valid?(payload, silkey_public_key = nil)
96
+ jwt_payload = Silkey::Models::JwtPayload.new.import(payload)
97
+
98
+ return nil if cant_validate_silky_signature?(jwt_payload)
99
+
100
+ return false unless silkey_sig_check_requirements?(jwt_payload)
101
+
102
+ signer = Silkey::Utils.verify_message(
103
+ jwt_payload.message_to_sign_by_silkey, jwt_payload.silkey_signature
104
+ )
105
+
106
+ if Silkey::Utils.empty?(silkey_public_key)
107
+ logger.warn('You are using verification without checking silkey signature. '\
108
+ 'We strongly recommended to turn on full verification. '\
109
+ 'This option can be deprecated in the future')
110
+ return true
111
+ end
112
+
113
+ success = signer.downcase == silkey_public_key.downcase
114
+
115
+ logger.warn("silkey_signature_valid?: expect #{signer} to be equal #{silkey_public_key}") unless success
116
+
117
+ success
118
+ rescue StandardError => e
119
+ logger.error(e)
120
+ false
121
+ end
122
+ # rubocop:enable Metrics/AbcSize
123
+
124
+ private
125
+
126
+ def scope_to_arr(scope)
127
+ raise 'scope is empty' if scope.empty?
128
+
129
+ return scope unless scope.is_a?(String)
130
+
131
+ arr = scope.split(Silkey::Settings.SCOPE_DIVIDER).reduce([]) do |acc, v|
132
+ return acc if v.empty?
133
+
134
+ acc.push(v)
135
+ end
136
+
137
+ raise 'scope is empty' if arr.empty?
138
+
139
+ arr
140
+ end
141
+
142
+ def can_validate_user_signature?(jwt_payload)
143
+ if Silkey::Utils.empty?(jwt_payload.address)
144
+ logger.warn('Verification failed, missing user address')
145
+ return false
146
+ end
147
+
148
+ if Silkey::Utils.empty?(jwt_payload.user_signature)
149
+ logger.warn('Verification failed, missing user signature')
150
+ return false
151
+ end
152
+
153
+ if Silkey::Utils.empty?(jwt_payload.user_signature_timestamp)
154
+ logger.warn('Verification failed, missing user signature timestamp ')
155
+ return false
156
+ end
157
+
158
+ true
159
+ end
160
+
161
+ def cant_validate_silky_signature?(jwt_payload)
162
+ empty_email = Silkey::Utils.empty?(jwt_payload.email)
163
+ empty_sig = Silkey::Utils.empty?(jwt_payload.silkey_signature)
164
+
165
+ empty_email && empty_sig
166
+ end
167
+
168
+ def silkey_sig_check_requirements?(jwt_payload)
169
+ empty_email = Silkey::Utils.empty?(jwt_payload.email)
170
+ empty_sig = Silkey::Utils.empty?(jwt_payload.silkey_signature)
171
+
172
+ if empty_email ^ empty_sig
173
+ logger.warn('Verification failed, missing silkey signature or email')
174
+ return false
175
+ end
176
+
177
+ if Silkey::Utils.empty?(jwt_payload.silkey_signature_timestamp)
178
+ logger.warn('Verification failed, missing silkey signature timestamp')
179
+ return false
180
+ end
181
+
182
+ true
183
+ end
184
+
185
+ def logger
186
+ Silkey::LoggerService
187
+ end
188
+ end
189
+ end
190
+ end
@@ -53,6 +53,12 @@ module Silkey
53
53
  Time.now.getutc.to_i
54
54
  end
55
55
 
56
+ def timestamp?(tmstp)
57
+ return false if tmstp.nil?
58
+
59
+ tmstp.positive? && tmstp.to_s(10).length == 10
60
+ end
61
+
56
62
  def int_to_hex(int)
57
63
  return '' if empty?(int)
58
64
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Silkey
4
- VERSION = '0.0.5'
4
+ VERSION = '0.1.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: silkey-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - silkey.io
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-15 00:00:00.000000000 Z
11
+ date: 2021-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -254,9 +254,12 @@ files:
254
254
  - lib/silkey/factories/client_factory.rb
255
255
  - lib/silkey/factories/contract_factory.rb
256
256
  - lib/silkey/models/jwt_payload.rb
257
+ - lib/silkey/models/settings.rb
258
+ - lib/silkey/models/sso_params.rb
257
259
  - lib/silkey/registry_contract/registry_contract.rb
258
260
  - lib/silkey/sdk.rb
259
261
  - lib/silkey/services/logger_service.rb
262
+ - lib/silkey/services/verifier.rb
260
263
  - lib/silkey/utils.rb
261
264
  - lib/silkey/version.rb
262
265
  - lib/silkey_sdk.rb
@@ -267,7 +270,7 @@ licenses:
267
270
  metadata:
268
271
  bug_tracker_uri: https://github.com/Silkey-Team/ruby-sdk/issues
269
272
  changelog_uri: https://github.com/Silkey-Team/ruby-sdk/blob/master/CHANGELOG.md
270
- documentation_uri: https://www.rubydoc.info/gems/silkey-sdk/0.0.5
273
+ documentation_uri: https://www.rubydoc.info/gems/silkey-sdk/0.1.0
271
274
  homepage_uri: https://silkey.io
272
275
  source_code_uri: https://github.com/Silkey-Team/ruby-sdk
273
276
  post_install_message: