silkey-sdk 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: