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 +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +15 -0
- data/DEVELOPMENT.md +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +25 -13
- data/lib/silkey-sdk.rb +3 -4
- data/lib/silkey/models/jwt_payload.rb +44 -41
- data/lib/silkey/models/settings.rb +34 -0
- data/lib/silkey/models/sso_params.rb +42 -0
- data/lib/silkey/sdk.rb +48 -136
- data/lib/silkey/services/verifier.rb +190 -0
- data/lib/silkey/utils.rb +6 -0
- data/lib/silkey/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac39da25efe706b56abfda8f580295dc0d0c7734ce83ccc13130b85cce5a1e22
|
|
4
|
+
data.tar.gz: 86744220f6d4e5ee274c1c451cc5be9591a60f240a5a2fc871d5fc3794d71988
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b634f6d573b002484a32a02059b730b5cd9184d93de5aaa67d9df981d833269845df561394444d239ea327ac653e68d2ffda4fa36a05e680b73e34d67384a065
|
|
7
|
+
data.tar.gz: 4024009b029a6d8aa45b7beb9d79a2d61c677d761d94ba79234539dc1aac9fe43e729735bd2df58b86cdfdcb8085c2fbbf70018624fbbb5504d9c85132c0ed22
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -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
|
data/DEVELOPMENT.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -27,31 +27,43 @@ end
|
|
|
27
27
|
|
|
28
28
|
#### Making request
|
|
29
29
|
|
|
30
|
-
| Parameter
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
| ssoTimestamp
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
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 = {
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
Silkey::SDK.token_payload_verifier(token,
|
|
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).
|
data/lib/silkey-sdk.rb
CHANGED
|
@@ -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 :
|
|
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
|
|
50
|
-
self.
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
115
|
-
|
|
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
|
|
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
|
-
|
|
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
|
data/lib/silkey/sdk.rb
CHANGED
|
@@ -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({
|
|
17
|
+
# Silkey::SDK.message_to_sign({ ssoRedirectUrl: 'http://silkey.io', ssoCancelUrl: 'http://silkey.io/fail' });
|
|
16
18
|
#
|
|
17
19
|
# returns
|
|
18
20
|
#
|
|
19
|
-
# '
|
|
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
|
-
|
|
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
|
|
37
|
-
# -
|
|
38
|
-
# -
|
|
39
|
-
# -
|
|
40
|
-
# -
|
|
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 = {
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
87
|
+
# {ssoSignature: '...', ...},
|
|
88
|
+
# website_eth_address,
|
|
89
|
+
# Silkey::SDK.fetch_silkey_eth_address
|
|
91
90
|
# )
|
|
92
91
|
#
|
|
93
|
-
def token_payload_verifier(token,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
113
|
-
|
|
123
|
+
def fetch_silkey_eth_address
|
|
124
|
+
silkey_address = Silkey::RegistryContract.get_address(Silkey::Settings.SILKEY_REGISTERED_BY_NAME)
|
|
114
125
|
|
|
115
|
-
raise "Invalid
|
|
126
|
+
raise "Invalid silkey address: #{silkey_address}" unless Silkey::Utils.ethereum_address?(silkey_address)
|
|
116
127
|
|
|
117
|
-
|
|
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
|
data/lib/silkey/utils.rb
CHANGED
data/lib/silkey/version.rb
CHANGED
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
|
|
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:
|
|
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
|
|
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:
|