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 +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:
|